Index: /tags/2.6.6/.editorconfig
===================================================================
--- /tags/2.6.6/.editorconfig	(revision 7162)
+++ /tags/2.6.6/.editorconfig	(revision 7162)
@@ -0,0 +1,21 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+# WordPress Coding Standards
+# https://make.wordpress.org/core/handbook/coding-standards/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = tab
+
+[*.yml]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
Index: /tags/2.6.6/.gitignore
===================================================================
--- /tags/2.6.6/.gitignore	(revision 7162)
+++ /tags/2.6.6/.gitignore	(revision 7162)
@@ -0,0 +1,19 @@
+# gitignore file for bbPress
+
+# Configuration files with possibly sensitive information
+.htaccess
+
+# Files and folders related to build/test tools
+/phpunit.xml
+/tests/phpunit/build
+/node_modules
+/npm-debug.log
+
+# Output directory.
+/build
+
+# Operating system specific files
+.DS_Store
+
+# Ignore other .gitignore files to allow for personal customizations
+.gitignore
Index: /tags/2.6.6/.jshintrc
===================================================================
--- /tags/2.6.6/.jshintrc	(revision 7162)
+++ /tags/2.6.6/.jshintrc	(revision 7162)
@@ -0,0 +1,22 @@
+{
+	"boss": true,
+	"curly": true,
+	"eqeqeq": true,
+	"eqnull": true,
+	"es3": true,
+	"expr": true,
+	"immed": true,
+	"noarg": true,
+	"quotmark": "single",
+	"undef": false,
+	"unused": true,
+
+	"browser": true,
+
+	"globals": {
+		"_": false,
+		"Backbone": false,
+		"jQuery": false,
+		"wp": false
+	}
+}
Index: /tags/2.6.6/.npmrc
===================================================================
--- /tags/2.6.6/.npmrc	(revision 7162)
+++ /tags/2.6.6/.npmrc	(revision 7162)
@@ -0,0 +1,1 @@
+save-prefix='~'
Index: /tags/2.6.6/.stylelintrc
===================================================================
--- /tags/2.6.6/.stylelintrc	(revision 7162)
+++ /tags/2.6.6/.stylelintrc	(revision 7162)
@@ -0,0 +1,7 @@
+{
+	"extends": "stylelint-config-wordpress/scss",
+	"rules": {
+		"font-family-no-missing-generic-family-keyword": null,
+		"no-descending-specificity": null
+	}
+}
Index: /tags/2.6.6/.travis.yml
===================================================================
--- /tags/2.6.6/.travis.yml	(revision 7162)
+++ /tags/2.6.6/.travis.yml	(revision 7162)
@@ -0,0 +1,115 @@
+# Travis CI Configuration File
+
+# Tell Travis CI we're using PHP
+language: php
+
+# Tell Travis CI which operating system to use
+os: linux
+
+# Ensure MySQL is available
+services:
+  - mysql
+
+# Configure caches
+cache:
+  directories:
+  - vendor
+  - $HOME/.composer/cache
+  - node_modules
+
+# PHP version used in first build configuration.
+php:
+  - 7.4
+  - 7.3
+  - 7.2
+  - 7.1
+  - 7.0
+  - 5.6
+
+# WordPress comes from the Git mirror, where 'master' mirrors svn 'trunk' and
+# x.y mirrors the latest from the x.y branch
+env:
+  - WP_VERSION=master
+  - WP_VERSION=5.3
+  - WP_VERSION=5.2
+
+# Build matrix options
+jobs:
+  include:
+    - php: nightly
+      env: WP_VERSION=master
+  exclude:
+    - php: 7.4
+      env: WP_VERSION=5.2
+  allow_failures:
+    - php: nightly
+#  fast_finish: true
+
+# before_install: Failures in this section will result in build status 'errored'
+before_install:
+    # setup WP_DEVELOP_DIR (needed for bbPress to bootstrap WP PHPUnit tests)
+    - export WP_DEVELOP_DIR=/tmp/wordpress/
+    - mkdir -p $WP_DEVELOP_DIR
+    # clone the WordPress develop repo
+    - git clone --depth=1 --branch="$WP_VERSION" git://develop.git.wordpress.org/ $WP_DEVELOP_DIR
+    # clone the BuddyPress develop repo
+    - git clone --depth=1 git://buddypress.git.wordpress.org/ $WP_DEVELOP_DIR/src/wp-content/plugins/buddypress
+    - plugin_slug=$(basename $(pwd))
+    - plugin_dir=$WP_DEVELOP_DIR/src/wp-content/plugins/$plugin_slug
+    - cd ..
+    - mv $plugin_slug $plugin_dir
+    # set up tests config
+    - cd $WP_DEVELOP_DIR
+    - echo $WP_DEVELOP_DIR
+    - cp wp-tests-config-sample.php wp-tests-config.php
+    - sed -i "s/youremptytestdbnamehere/wordpress_test/" wp-tests-config.php
+    - sed -i "s/yourusernamehere/root/" wp-tests-config.php
+    - sed -i "s/yourpasswordhere//" wp-tests-config.php
+    # set up database
+    - mysql -e 'CREATE DATABASE wordpress_test;' -uroot
+    # prepare for running the tests
+    - cd $plugin_dir
+    # setup NodeJS version using NVM
+    - node --version
+    - nvm install 8
+    - node --version
+    - npm install -g grunt-cli
+    - npm --version
+    - phpenv versions
+    - echo $TRAVIS_PHP_VERSION
+    - mysql --version
+    - php --version
+    - php -m
+    - phpunit --version
+    - curl --version
+    - grunt --version
+    - git --version
+    - svn --version
+
+# before_script: Failures in this section will result in build status 'failed'
+before_script:
+  - |
+    # Remove Xdebug for a huge performance increase:
+    if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then
+      phpenv config-rm xdebug.ini
+    else
+      echo "xdebug.ini does not exist"
+    fi
+  - export PATH="$HOME/.composer/vendor/bin:$PATH"
+  - |
+    if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then
+      echo "Using latest PHPUnit 6.x branch"
+      composer global require "phpunit/phpunit=^6"
+    elif [[ ${TRAVIS_PHP_VERSION:0:3} == "5.6" ]]; then
+      echo "Using latest PHPUnit 4.x branch"
+      composer global require "phpunit/phpunit=^4"
+    fi
+  - npm install
+  - grunt build
+
+# Run tests
+script:
+  - grunt travis
+
+notifications:
+    email: false
Index: /tags/2.6.6/Gruntfile.js
===================================================================
--- /tags/2.6.6/Gruntfile.js	(revision 7162)
+++ /tags/2.6.6/Gruntfile.js	(revision 7162)
@@ -0,0 +1,446 @@
+/* jshint node:true */
+/* global module */
+module.exports = function( grunt ) {
+	var path = require( 'path' ),
+		SOURCE_DIR = 'src/',
+		BUILD_DIR = 'build/',
+
+		BBP_RTL_CSS = [
+			'includes/admin/assets/css/admin-rtl.css',
+			'includes/admin/styles/*/colors-rtl.css',
+			'templates/default/css/bbpress-rtl.css'
+		],
+
+		BBP_LTR_CSS = [
+			'includes/admin/assets/css/admin.css',
+			'includes/admin/styles/*/colors.css',
+			'templates/default/css/bbpress.css'
+		],
+
+		BBP_JS = [
+			'includes/admin/assets/js/*.js',
+			'templates/default/js/*.js'
+		],
+
+		BBP_EXCLUDED_FILES = [
+
+			// Ignore these
+			'!**/.{svn,git}/**',
+			'!.editorconfig',
+			'!.gitignore',
+			'!.jshintrc',
+			'!.travis.yml',
+			'!build/**',
+			'!Gruntfile.js',
+			'!node_modules/**',
+			'!npm-debug.log',
+			'!package.json',
+
+			// bbPress SCSS CSS source files
+			'!**/*.scss',
+
+			// bbPress PHPUnit tests
+			'!tests/**',
+			'!phpunit.xml',
+			'!phpunit.xml.dist'
+		],
+
+		// PostCSS
+		autoprefixer = require('autoprefixer');
+
+	// Load tasks.
+	require( 'matchdep' ).filterDev([ 'grunt-*', '!grunt-legacy-util' ]).forEach( grunt.loadNpmTasks );
+
+	// Load legacy utils
+	grunt.util = require( 'grunt-legacy-util' );
+
+	// Project configuration.
+	grunt.initConfig({
+		pkg: grunt.file.readJSON( 'package.json' ),
+		checktextdomain: {
+			options: {
+				text_domain: 'bbpress',
+				correct_domain: false,
+				keywords: [
+					'__:1,2d',
+					'_e:1,2d',
+					'_x:1,2c,3d',
+					'_n:1,2,4d',
+					'_ex:1,2c,3d',
+					'_nx:1,2,4c,5d',
+					'esc_attr__:1,2d',
+					'esc_attr_e:1,2d',
+					'esc_attr_x:1,2c,3d',
+					'esc_html__:1,2d',
+					'esc_html_e:1,2d',
+					'esc_html_x:1,2c,3d',
+					'_n_noop:1,2,3d',
+					'_nx_noop:1,2,3c,4d'
+				]
+			},
+			files: {
+				src: SOURCE_DIR + '**/*.php',
+				expand: true
+			}
+		},
+		clean: {
+			all: [ BUILD_DIR ],
+			dynamic: {
+				cwd: BUILD_DIR,
+				dot: true,
+				expand: true,
+				src: []
+			}
+		},
+		checkDependencies: {
+			options: {
+				packageManager: 'npm'
+			},
+			src: {}
+		},
+		copy: {
+			files: {
+				files: [
+					{
+						cwd: SOURCE_DIR,
+						dest: BUILD_DIR,
+						dot: true,
+						expand: true,
+						src: [ '!**/.{svn,git}/**', '**', [ BBP_EXCLUDED_FILES ] ]
+					}
+				]
+			},
+			dynamic: {
+				cwd: SOURCE_DIR,
+				dest: BUILD_DIR,
+				dot: true,
+				expand: true,
+				src: []
+			}
+		},
+		cssmin: {
+			ltr: {
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				expand: true,
+				ext: '.min.css',
+				src: BBP_LTR_CSS
+			},
+			rtl: {
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				expand: true,
+				ext: '.min.css',
+				src: BBP_RTL_CSS
+			}
+		},
+		jshint: {
+			options: grunt.file.readJSON( '.jshintrc' ),
+			grunt: {
+				src: [ 'Gruntfile.js' ]
+			},
+			core: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				src: BBP_JS,
+
+				/**
+				 * Limit JSHint's run to a single specified file:
+				 *
+				 * grunt jshint:core --file=filename.js
+				 *
+				 * Optionally, include the file path:
+				 *
+				 * grunt jshint:core --file=path/to/filename.js
+				 *
+				 * @param {String} filepath
+				 * @returns {Bool}
+				 */
+				filter: function( filepath ) {
+					var index, file = grunt.option( 'file' );
+
+					// Don't filter when no target file is specified
+					if ( ! file ) {
+						return true;
+					}
+
+					// Normalise filepath for Windows
+					filepath = filepath.replace( /\\/g, '/' );
+					index = filepath.lastIndexOf( '/' + file );
+
+					// Match only the filename passed from cli
+					if ( filepath === file || ( -1 !== index && index === filepath.length - ( file.length + 1 ) ) ) {
+						return true;
+					}
+
+					return false;
+				}
+			}
+		},
+		jsvalidate:{
+			options:{
+				globals: {},
+				esprimaOptions:{},
+				verbose: false
+			},
+			build: {
+				files: {
+					src: BUILD_DIR + '/**/*.js'
+				}
+			},
+			src: {
+				files: {
+					src: SOURCE_DIR + '/**/*.js'
+				}
+			}
+		},
+		makepot: {
+			target: {
+				options: {
+					cwd: BUILD_DIR,
+					domainPath: '.',
+					mainFile: 'bbpress.php',
+					potFilename: 'bbpress.pot',
+					processPot: function( pot ) {
+						pot.headers['report-msgid-bugs-to'] = 'https://bbpress.trac.wordpress.org';
+						pot.headers['last-translator'] = 'JOHN JAMES JACOBY <jjj@bbpress.org>';
+						pot.headers['language-team'] = 'ENGLISH <jjj@bbpress.org>';
+						return pot;
+					},
+					type: 'wp-plugin'
+				}
+			}
+		},
+		patch: {
+			options: {
+				tracUrl: 'bbpress.trac.wordpress.org'
+			}
+		},
+		upload_patch: {
+			options: {
+				tracUrl: 'bbpress.trac.wordpress.org'
+			}
+		},
+		phpcs: {
+			'default': {
+				cmd: 'phpcs',
+				args: [ '--report-summary', '--report-source', '--cache=.phpcscache' ]
+			}
+		},
+		phpunit: {
+			'default': {
+				cmd: 'phpunit',
+				args: [ '-c', 'phpunit.xml.dist' ]
+			},
+			buddypress: {
+				cmd: 'phpunit',
+				args: [ '-c', 'tests/phpunit/buddypress.xml' ]
+			},
+			multisite: {
+				cmd: 'phpunit',
+				args: [ '-c', 'tests/phpunit/multisite.xml' ]
+			}
+		},
+		postcss: {
+			options: {
+				map: false,
+				processors: [
+					autoprefixer({
+						browsers: ['extends @wordpress/browserslist-config'],
+						cascade: false
+					})
+				],
+				failOnError: false
+			},
+			core: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				dest: SOURCE_DIR,
+				src: [ [ BBP_LTR_CSS ], 'includes/admin/styles/*/colors.scss' ]
+			},
+			colors: {
+				expand: true,
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				src: [ 'includes/admin/styles/*/colors.css' ]
+			}
+		},
+		rtlcss: {
+			options: {
+				opts: {
+					processUrls: false,
+					autoRename: false,
+					clean: true
+				},
+				saveUnmodified: false
+			},
+			core: {
+				expand: true,
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				ext: '-rtl.css',
+				src: BBP_LTR_CSS
+			},
+			dynamic: {
+				expand: true,
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				ext: '-rtl.css',
+				src: []
+			}
+		},
+		sass: {
+			colors: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				dest: BUILD_DIR,
+				ext: '.css',
+				src: [ 'includes/admin/styles/*/colors.scss' ],
+				options: {
+					implementation: require('node-sass'),
+					outputStyle: 'expanded'
+				}
+			}
+		},
+		stylelint: {
+			css: {
+				options: {
+					format: 'css'
+				},
+				expand: true,
+				cwd: SOURCE_DIR,
+				src: BBP_LTR_CSS
+			},
+			scss: {
+				options: {
+					format: 'scss'
+				},
+				expand: true,
+				cwd: SOURCE_DIR,
+				src: [ 'includes/admin/styles/*/colors.scss' ]
+			}
+		},
+		uglify: {
+			core: {
+				cwd: SOURCE_DIR,
+				dest: BUILD_DIR,
+				expand: true,
+				ext: '.min.js',
+				src: BBP_JS
+			},
+			dynamic: {
+				cwd: SOURCE_DIR,
+				dest: BUILD_DIR,
+				expand: true,
+				ext: '.min.js',
+				src: []
+			},
+			options: {
+				banner: '/*! This file is automatically generated. */\n'
+			}
+		},
+		watch: {
+			all: {
+				files: [
+					SOURCE_DIR + '**',
+
+					// Ignore version control directories.
+					'!' + SOURCE_DIR + '**/.{svn,git}/**'
+				],
+				tasks: [ 'clean:dynamic', 'copy:dynamic' ],
+				options: {
+					dot: true,
+					interval: 2000,
+					spawn: false
+				}
+			},
+			colors: {
+				files: [ SOURCE_DIR + 'includes/admin/styles/*/colors.scss' ],
+				tasks: [ 'sass:colors', 'rtlcss:core', 'cssmin:ltr', 'cssmin:rtl' ]
+			},
+			config: {
+				files: 'Gruntfile.js'
+			},
+			js: {
+				files: BBP_JS.map( function( path ) {
+					return SOURCE_DIR + path;
+				} ),
+				tasks: [ 'uglify:dynamic' ],
+				options: {
+					interval: 2000,
+					spawn: false
+				}
+			},
+			rtl: {
+				files: BBP_LTR_CSS.map( function( path ) {
+					return SOURCE_DIR + path;
+				} ),
+				tasks: [ 'rtlcss:dynamic', 'cssmin:ltr', 'cssmin:rtl' ],
+				options: {
+					interval: 2000,
+					spawn: false
+				}
+			}
+		}
+	});
+
+	// Register tasks.
+
+	// Color schemes task.
+	grunt.registerTask( 'colors', [ 'sass:colors', 'postcss:colors' ] );
+
+	// Build tasks.
+	grunt.registerTask( 'src',     [ 'checkDependencies', 'jsvalidate:src', 'jshint', 'stylelint' ] );
+	grunt.registerTask( 'commit',  [ 'src', 'checktextdomain', 'postcss:core' ] );
+	grunt.registerTask( 'build',   [ 'commit', 'clean:all', 'copy:files', 'colors', 'rtlcss:core', 'cssmin:ltr', 'cssmin:rtl', 'uglify:core', 'jsvalidate:build', 'makepot' ] );
+	grunt.registerTask( 'release', [ 'build' ] );
+
+	// PHPCS test task.
+	grunt.registerMultiTask( 'phpcs', 'Runs PHPCS tests.', function() {
+		grunt.util.spawn( {
+			cmd:  this.data.cmd,
+			args: this.data.args,
+			opts: { stdio: 'inherit' }
+		}, this.async() );
+	} );
+
+	// PHPUnit test task.
+	grunt.registerMultiTask( 'phpunit', 'Runs PHPUnit tests, including the BuddyPress and multisite tests.', function() {
+		grunt.util.spawn( {
+			cmd:  this.data.cmd,
+			args: this.data.args,
+			opts: { stdio: 'inherit' }
+		}, this.async() );
+	} );
+
+	// JavaScript test task.
+	grunt.registerTask( 'jstest', 'Runs all JavaScript tasks.', [ 'jsvalidate:src', 'jshint' ] );
+
+	// Travis CI Task
+	grunt.registerTask( 'travis', [ 'jsvalidate:src', 'jshint', 'checktextdomain', 'phpunit' ] );
+
+	// Patch task.
+	grunt.renameTask( 'patch_wordpress', 'patch' );
+
+	// Default task.
+	grunt.registerTask( 'default', [ 'src' ] );
+
+	// Add a listener to the watch task.
+	//
+	// On `watch:all`, automatically updates the `copy:dynamic` and `clean:dynamic` configurations so that only the changed files are updated.
+	// On `watch:rtl`, automatically updates the `rtlcss:dynamic` configuration.
+	grunt.event.on( 'watch', function( action, filepath, target ) {
+		if ( target !== 'all' && target !== 'rtl' ) {
+			return;
+		}
+
+		var relativePath = path.relative( SOURCE_DIR, filepath ),
+		cleanSrc = ( action === 'deleted' ) ? [ relativePath ] : [],
+		copySrc  = ( action === 'deleted' ) ? [] : [ relativePath ];
+
+		grunt.config( [ 'clean', 'dynamic', 'src' ], cleanSrc );
+		grunt.config( [ 'copy', 'dynamic', 'src' ], copySrc );
+		grunt.config( [ 'rtlcss', 'dynamic', 'src' ], copySrc );
+		grunt.config( [ 'uglify', 'dynamic', 'src' ], copySrc );
+	});
+};
Index: /tags/2.6.6/bbpress.php
===================================================================
--- /tags/2.6.6/bbpress.php	(revision 7162)
+++ /tags/2.6.6/bbpress.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * The bbPress Plugin
+ *
+ * bbPress is forum software with a twist from the creators of WordPress.
+ *
+ * $Id$
+ *
+ * @package bbPress
+ * @subpackage Main
+ */
+
+/**
+ * Plugin Name:       bbPress
+ * Plugin URI:        https://bbpress.org
+ * Description:       bbPress is forum software with a twist from the creators of WordPress.
+ * Author:            The bbPress Contributors
+ * Author URI:        https://bbpress.org
+ * Version:           2.6.6
+ * Text Domain:       bbpress
+ * Domain Path:       /languages/
+ * License:           GPLv2 or later (license.txt)
+ * Requires PHP:      5.6.20
+ * Requires at least: 5.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Assume you want to load from build
+$bbp_loader = __DIR__ . '/build/bbpress.php';
+
+// Load from source if no build exists
+if ( ! file_exists( $bbp_loader ) || defined( 'BBP_LOAD_SOURCE' ) ) {
+	$bbp_loader = __DIR__ . '/src/bbpress.php';
+}
+
+// Include bbPress
+include $bbp_loader;
+
+// Unset the loader, since it's loaded in global scope
+unset( $bbp_loader );
Index: /tags/2.6.6/package-lock.json
===================================================================
--- /tags/2.6.6/package-lock.json	(revision 7162)
+++ /tags/2.6.6/package-lock.json	(revision 7162)
@@ -0,0 +1,6840 @@
+{
+	"name": "bbpress",
+	"version": "2.6.6",
+	"lockfileVersion": 1,
+	"requires": true,
+	"dependencies": {
+		"@babel/code-frame": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
+			"integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
+			"dev": true,
+			"requires": {
+				"@babel/highlight": "^7.10.1"
+			}
+		},
+		"@babel/core": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.1.tgz",
+			"integrity": "sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg==",
+			"dev": true,
+			"requires": {
+				"@babel/code-frame": "^7.10.1",
+				"@babel/generator": "^7.10.1",
+				"@babel/helper-module-transforms": "^7.10.1",
+				"@babel/helpers": "^7.10.1",
+				"@babel/parser": "^7.10.1",
+				"@babel/template": "^7.10.1",
+				"@babel/traverse": "^7.10.1",
+				"@babel/types": "^7.10.1",
+				"convert-source-map": "^1.7.0",
+				"debug": "^4.1.0",
+				"gensync": "^1.0.0-beta.1",
+				"json5": "^2.1.2",
+				"lodash": "^4.17.13",
+				"resolve": "^1.3.2",
+				"semver": "^5.4.1",
+				"source-map": "^0.5.0"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"ms": {
+					"version": "2.1.2",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+					"dev": true
+				},
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				}
+			}
+		},
+		"@babel/generator": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.1.tgz",
+			"integrity": "sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1",
+				"jsesc": "^2.5.1",
+				"lodash": "^4.17.13",
+				"source-map": "^0.5.0"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				}
+			}
+		},
+		"@babel/helper-function-name": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz",
+			"integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==",
+			"dev": true,
+			"requires": {
+				"@babel/helper-get-function-arity": "^7.10.1",
+				"@babel/template": "^7.10.1",
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-get-function-arity": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
+			"integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-member-expression-to-functions": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
+			"integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-module-imports": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz",
+			"integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-module-transforms": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz",
+			"integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==",
+			"dev": true,
+			"requires": {
+				"@babel/helper-module-imports": "^7.10.1",
+				"@babel/helper-replace-supers": "^7.10.1",
+				"@babel/helper-simple-access": "^7.10.1",
+				"@babel/helper-split-export-declaration": "^7.10.1",
+				"@babel/template": "^7.10.1",
+				"@babel/types": "^7.10.1",
+				"lodash": "^4.17.13"
+			}
+		},
+		"@babel/helper-optimise-call-expression": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
+			"integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-replace-supers": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz",
+			"integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==",
+			"dev": true,
+			"requires": {
+				"@babel/helper-member-expression-to-functions": "^7.10.1",
+				"@babel/helper-optimise-call-expression": "^7.10.1",
+				"@babel/traverse": "^7.10.1",
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-simple-access": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz",
+			"integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==",
+			"dev": true,
+			"requires": {
+				"@babel/template": "^7.10.1",
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-split-export-declaration": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz",
+			"integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==",
+			"dev": true,
+			"requires": {
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/helper-validator-identifier": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
+			"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
+			"dev": true
+		},
+		"@babel/helpers": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz",
+			"integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==",
+			"dev": true,
+			"requires": {
+				"@babel/template": "^7.10.1",
+				"@babel/traverse": "^7.10.1",
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/highlight": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
+			"integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
+			"dev": true,
+			"requires": {
+				"@babel/helper-validator-identifier": "^7.10.1",
+				"chalk": "^2.0.0",
+				"js-tokens": "^4.0.0"
+			}
+		},
+		"@babel/parser": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz",
+			"integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==",
+			"dev": true
+		},
+		"@babel/template": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz",
+			"integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==",
+			"dev": true,
+			"requires": {
+				"@babel/code-frame": "^7.10.1",
+				"@babel/parser": "^7.10.1",
+				"@babel/types": "^7.10.1"
+			}
+		},
+		"@babel/traverse": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz",
+			"integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==",
+			"dev": true,
+			"requires": {
+				"@babel/code-frame": "^7.10.1",
+				"@babel/generator": "^7.10.1",
+				"@babel/helper-function-name": "^7.10.1",
+				"@babel/helper-split-export-declaration": "^7.10.1",
+				"@babel/parser": "^7.10.1",
+				"@babel/types": "^7.10.1",
+				"debug": "^4.1.0",
+				"globals": "^11.1.0",
+				"lodash": "^4.17.13"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"ms": {
+					"version": "2.1.2",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+					"dev": true
+				}
+			}
+		},
+		"@babel/types": {
+			"version": "7.10.1",
+			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
+			"integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
+			"dev": true,
+			"requires": {
+				"@babel/helper-validator-identifier": "^7.10.1",
+				"lodash": "^4.17.13",
+				"to-fast-properties": "^2.0.0"
+			}
+		},
+		"@choojs/findup": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz",
+			"integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==",
+			"dev": true,
+			"requires": {
+				"commander": "^2.15.1"
+			},
+			"dependencies": {
+				"commander": {
+					"version": "2.20.3",
+					"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+					"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+					"dev": true
+				}
+			}
+		},
+		"@nodelib/fs.scandir": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
+			"integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "2.0.3",
+				"run-parallel": "^1.1.9"
+			}
+		},
+		"@nodelib/fs.stat": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
+			"integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
+			"dev": true
+		},
+		"@nodelib/fs.walk": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
+			"integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.scandir": "2.1.3",
+				"fastq": "^1.6.0"
+			}
+		},
+		"@stylelint/postcss-css-in-js": {
+			"version": "0.37.1",
+			"resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz",
+			"integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==",
+			"dev": true,
+			"requires": {
+				"@babel/core": ">=7.9.0"
+			}
+		},
+		"@stylelint/postcss-markdown": {
+			"version": "0.36.1",
+			"resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz",
+			"integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==",
+			"dev": true,
+			"requires": {
+				"remark": "^12.0.0",
+				"unist-util-find-all-after": "^3.0.1"
+			}
+		},
+		"@types/color-name": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+			"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+			"dev": true
+		},
+		"@types/minimist": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
+			"integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=",
+			"dev": true
+		},
+		"@types/normalize-package-data": {
+			"version": "2.4.0",
+			"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+			"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
+			"dev": true
+		},
+		"@types/parse-json": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
+			"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
+			"dev": true
+		},
+		"@types/unist": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
+			"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==",
+			"dev": true
+		},
+		"@wordpress/browserslist-config": {
+			"version": "2.1.4",
+			"resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-2.1.4.tgz",
+			"integrity": "sha512-J8vd88IFsjYwYZSIVATDjWa0pH997zvQ6quor30UdwhqdZ4ZWxIh4AEADqZA3I4EwskWVmg9n9DF7jVUDZPxwg==",
+			"dev": true
+		},
+		"abbrev": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+			"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+			"dev": true
+		},
+		"ajv": {
+			"version": "6.10.2",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+			"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+			"dev": true,
+			"requires": {
+				"fast-deep-equal": "^2.0.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			}
+		},
+		"amdefine": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+			"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+			"dev": true
+		},
+		"ansi-escapes": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+			"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+			"dev": true
+		},
+		"ansi-regex": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+			"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+			"dev": true
+		},
+		"ansi-styles": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+			"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+			"dev": true,
+			"requires": {
+				"color-convert": "^1.9.0"
+			}
+		},
+		"aproba": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+			"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+			"dev": true
+		},
+		"are-we-there-yet": {
+			"version": "1.1.5",
+			"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+			"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+			"dev": true,
+			"requires": {
+				"delegates": "^1.0.0",
+				"readable-stream": "^2.0.6"
+			},
+			"dependencies": {
+				"readable-stream": {
+					"version": "2.3.7",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+					"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+					"dev": true,
+					"requires": {
+						"core-util-is": "~1.0.0",
+						"inherits": "~2.0.3",
+						"isarray": "~1.0.0",
+						"process-nextick-args": "~2.0.0",
+						"safe-buffer": "~5.1.1",
+						"string_decoder": "~1.1.1",
+						"util-deprecate": "~1.0.1"
+					}
+				},
+				"string_decoder": {
+					"version": "1.1.1",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+					"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+					"dev": true,
+					"requires": {
+						"safe-buffer": "~5.1.0"
+					}
+				}
+			}
+		},
+		"argparse": {
+			"version": "1.0.10",
+			"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+			"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+			"dev": true,
+			"requires": {
+				"sprintf-js": "~1.0.2"
+			},
+			"dependencies": {
+				"sprintf-js": {
+					"version": "1.0.3",
+					"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+					"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+					"dev": true
+				}
+			}
+		},
+		"arr-diff": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+			"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+			"dev": true
+		},
+		"arr-flatten": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+			"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+			"dev": true
+		},
+		"arr-union": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+			"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+			"dev": true
+		},
+		"array-find-index": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+			"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+			"dev": true
+		},
+		"array-union": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+			"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+			"dev": true
+		},
+		"array-unique": {
+			"version": "0.3.2",
+			"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+			"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+			"dev": true
+		},
+		"arrify": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+			"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+			"dev": true
+		},
+		"asn1": {
+			"version": "0.2.4",
+			"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+			"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+			"dev": true,
+			"requires": {
+				"safer-buffer": "~2.1.0"
+			}
+		},
+		"assert-plus": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+			"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+			"dev": true
+		},
+		"assign-symbols": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+			"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+			"dev": true
+		},
+		"astral-regex": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+			"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+			"dev": true
+		},
+		"async": {
+			"version": "1.5.2",
+			"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+			"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+			"dev": true
+		},
+		"async-foreach": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+			"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+			"dev": true
+		},
+		"asynckit": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+			"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+			"dev": true
+		},
+		"atob": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+			"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+			"dev": true
+		},
+		"autoprefixer": {
+			"version": "8.2.0",
+			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.2.0.tgz",
+			"integrity": "sha512-xBVQpGAcSNNS1PBnEfT+F9VF8ZJeoKZ121I3OVQ0n1F0SqVuj4oLI6yFeEviPV8Z/GjoqBRXcYis0oSS8zjNEg==",
+			"dev": true,
+			"requires": {
+				"browserslist": "^3.2.0",
+				"caniuse-lite": "^1.0.30000817",
+				"normalize-range": "^0.1.2",
+				"num2fraction": "^1.2.2",
+				"postcss": "^6.0.20",
+				"postcss-value-parser": "^3.2.3"
+			}
+		},
+		"aws-sign2": {
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+			"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+			"dev": true
+		},
+		"aws4": {
+			"version": "1.8.0",
+			"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+			"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
+			"dev": true
+		},
+		"bail": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
+			"integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==",
+			"dev": true
+		},
+		"balanced-match": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+			"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+			"dev": true
+		},
+		"base": {
+			"version": "0.11.2",
+			"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+			"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+			"dev": true,
+			"requires": {
+				"cache-base": "^1.0.1",
+				"class-utils": "^0.3.5",
+				"component-emitter": "^1.2.1",
+				"define-property": "^1.0.0",
+				"isobject": "^3.0.1",
+				"mixin-deep": "^1.2.0",
+				"pascalcase": "^0.1.1"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+					"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^1.0.0"
+					}
+				},
+				"is-accessor-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+					"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-data-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+					"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-descriptor": {
+					"version": "1.0.2",
+					"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+					"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+					"dev": true,
+					"requires": {
+						"is-accessor-descriptor": "^1.0.0",
+						"is-data-descriptor": "^1.0.0",
+						"kind-of": "^6.0.2"
+					}
+				}
+			}
+		},
+		"bcrypt-pbkdf": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+			"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+			"dev": true,
+			"requires": {
+				"tweetnacl": "^0.14.3"
+			}
+		},
+		"block-stream": {
+			"version": "0.0.9",
+			"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+			"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+			"dev": true,
+			"requires": {
+				"inherits": "~2.0.0"
+			}
+		},
+		"bluebird": {
+			"version": "3.7.1",
+			"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
+			"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==",
+			"dev": true
+		},
+		"body": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz",
+			"integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=",
+			"dev": true,
+			"requires": {
+				"continuable-cache": "^0.3.1",
+				"error": "^7.0.0",
+				"raw-body": "~1.1.0",
+				"safe-json-parse": "~1.0.1"
+			}
+		},
+		"bower-config": {
+			"version": "1.4.3",
+			"resolved": "https://registry.npmjs.org/bower-config/-/bower-config-1.4.3.tgz",
+			"integrity": "sha512-MVyyUk3d1S7d2cl6YISViwJBc2VXCkxF5AUFykvN0PQj5FsUiMNSgAYTso18oRFfyZ6XEtjrgg9MAaufHbOwNw==",
+			"dev": true,
+			"requires": {
+				"graceful-fs": "^4.1.3",
+				"minimist": "^0.2.1",
+				"mout": "^1.0.0",
+				"osenv": "^0.1.3",
+				"untildify": "^2.1.0",
+				"wordwrap": "^0.0.3"
+			},
+			"dependencies": {
+				"minimist": {
+					"version": "0.2.1",
+					"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz",
+					"integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==",
+					"dev": true
+				}
+			}
+		},
+		"brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"requires": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
+		"braces": {
+			"version": "2.3.2",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+			"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+			"dev": true,
+			"requires": {
+				"arr-flatten": "^1.1.0",
+				"array-unique": "^0.3.2",
+				"extend-shallow": "^2.0.1",
+				"fill-range": "^4.0.0",
+				"isobject": "^3.0.1",
+				"repeat-element": "^1.1.2",
+				"snapdragon": "^0.8.1",
+				"snapdragon-node": "^2.0.1",
+				"split-string": "^3.0.2",
+				"to-regex": "^3.0.1"
+			},
+			"dependencies": {
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				}
+			}
+		},
+		"browserify-zlib": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
+			"integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=",
+			"dev": true,
+			"requires": {
+				"pako": "~0.2.0"
+			}
+		},
+		"browserslist": {
+			"version": "3.2.8",
+			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz",
+			"integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==",
+			"dev": true,
+			"requires": {
+				"caniuse-lite": "^1.0.30000844",
+				"electron-to-chromium": "^1.3.47"
+			}
+		},
+		"buffer-from": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+			"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+			"dev": true
+		},
+		"bytes": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz",
+			"integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=",
+			"dev": true
+		},
+		"cache-base": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+			"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+			"dev": true,
+			"requires": {
+				"collection-visit": "^1.0.0",
+				"component-emitter": "^1.2.1",
+				"get-value": "^2.0.6",
+				"has-value": "^1.0.0",
+				"isobject": "^3.0.1",
+				"set-value": "^2.0.0",
+				"to-object-path": "^0.3.0",
+				"union-value": "^1.0.0",
+				"unset-value": "^1.0.0"
+			}
+		},
+		"callsites": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+			"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+			"dev": true
+		},
+		"camelcase": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+			"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+			"dev": true
+		},
+		"camelcase-keys": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+			"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+			"dev": true,
+			"requires": {
+				"camelcase": "^2.0.0",
+				"map-obj": "^1.0.0"
+			}
+		},
+		"caniuse-lite": {
+			"version": "1.0.30000999",
+			"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz",
+			"integrity": "sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==",
+			"dev": true
+		},
+		"caseless": {
+			"version": "0.12.0",
+			"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+			"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+			"dev": true
+		},
+		"ccount": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz",
+			"integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==",
+			"dev": true
+		},
+		"chalk": {
+			"version": "2.4.2",
+			"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+			"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+			"dev": true,
+			"requires": {
+				"ansi-styles": "^3.2.1",
+				"escape-string-regexp": "^1.0.5",
+				"supports-color": "^5.3.0"
+			}
+		},
+		"character-entities": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+			"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+			"dev": true
+		},
+		"character-entities-html4": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz",
+			"integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==",
+			"dev": true
+		},
+		"character-entities-legacy": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+			"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+			"dev": true
+		},
+		"character-reference-invalid": {
+			"version": "1.1.4",
+			"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+			"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+			"dev": true
+		},
+		"chardet": {
+			"version": "0.4.2",
+			"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
+			"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
+			"dev": true
+		},
+		"check-dependencies": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/check-dependencies/-/check-dependencies-1.1.0.tgz",
+			"integrity": "sha512-GDrbGzzJ6Gc6tQh87HBMGhrJ4UWIlR9MKJwgvlrJyj/gWvTYYb2jQetKbajt/EYK5Y8/4g7gH2LEvq8GdUWTag==",
+			"dev": true,
+			"requires": {
+				"bower-config": "^1.4.0",
+				"chalk": "^2.1.0",
+				"findup-sync": "^2.0.0",
+				"lodash.camelcase": "^4.3.0",
+				"minimist": "^1.2.0",
+				"semver": "^5.4.1"
+			},
+			"dependencies": {
+				"findup-sync": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
+					"integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
+					"dev": true,
+					"requires": {
+						"detect-file": "^1.0.0",
+						"is-glob": "^3.1.0",
+						"micromatch": "^3.0.4",
+						"resolve-dir": "^1.0.1"
+					}
+				}
+			}
+		},
+		"class-utils": {
+			"version": "0.3.6",
+			"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+			"integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+			"dev": true,
+			"requires": {
+				"arr-union": "^3.1.0",
+				"define-property": "^0.2.5",
+				"isobject": "^3.0.0",
+				"static-extend": "^0.1.1"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "0.2.5",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+					"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^0.1.0"
+					}
+				}
+			}
+		},
+		"clean-css": {
+			"version": "4.1.11",
+			"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz",
+			"integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=",
+			"dev": true,
+			"requires": {
+				"source-map": "0.5.x"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				}
+			}
+		},
+		"cli": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+			"integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
+			"dev": true,
+			"requires": {
+				"exit": "0.1.2",
+				"glob": "^7.1.1"
+			},
+			"dependencies": {
+				"glob": {
+					"version": "7.1.4",
+					"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+					"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+					"dev": true,
+					"requires": {
+						"fs.realpath": "^1.0.0",
+						"inflight": "^1.0.4",
+						"inherits": "2",
+						"minimatch": "^3.0.4",
+						"once": "^1.3.0",
+						"path-is-absolute": "^1.0.0"
+					}
+				}
+			}
+		},
+		"cli-cursor": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+			"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+			"dev": true,
+			"requires": {
+				"restore-cursor": "^2.0.0"
+			}
+		},
+		"cli-width": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+			"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+			"dev": true
+		},
+		"cliui": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+			"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+			"dev": true,
+			"requires": {
+				"string-width": "^3.1.0",
+				"strip-ansi": "^5.2.0",
+				"wrap-ansi": "^5.1.0"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+					"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+					"dev": true
+				},
+				"emoji-regex": {
+					"version": "7.0.3",
+					"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+					"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+					"dev": true
+				},
+				"string-width": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+					"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^7.0.1",
+						"is-fullwidth-code-point": "^2.0.0",
+						"strip-ansi": "^5.1.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+					"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^4.1.0"
+					}
+				}
+			}
+		},
+		"clone-regexp": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz",
+			"integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==",
+			"dev": true,
+			"requires": {
+				"is-regexp": "^2.0.0"
+			}
+		},
+		"code-point-at": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+			"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+			"dev": true
+		},
+		"coffeescript": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz",
+			"integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=",
+			"dev": true
+		},
+		"collapse-white-space": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
+			"integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==",
+			"dev": true
+		},
+		"collection-visit": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+			"integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+			"dev": true,
+			"requires": {
+				"map-visit": "^1.0.0",
+				"object-visit": "^1.0.0"
+			}
+		},
+		"color-convert": {
+			"version": "1.9.3",
+			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+			"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+			"dev": true,
+			"requires": {
+				"color-name": "1.1.3"
+			}
+		},
+		"color-name": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+			"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+			"dev": true
+		},
+		"colors": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+			"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+			"dev": true
+		},
+		"combined-stream": {
+			"version": "1.0.8",
+			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"dev": true,
+			"requires": {
+				"delayed-stream": "~1.0.0"
+			}
+		},
+		"commander": {
+			"version": "2.11.0",
+			"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+			"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+			"dev": true
+		},
+		"component-emitter": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+			"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+			"dev": true
+		},
+		"concat-map": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+			"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+			"dev": true
+		},
+		"concat-stream": {
+			"version": "1.6.2",
+			"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+			"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+			"dev": true,
+			"requires": {
+				"buffer-from": "^1.0.0",
+				"inherits": "^2.0.3",
+				"readable-stream": "^2.2.2",
+				"typedarray": "^0.0.6"
+			},
+			"dependencies": {
+				"readable-stream": {
+					"version": "2.3.6",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+					"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+					"dev": true,
+					"requires": {
+						"core-util-is": "~1.0.0",
+						"inherits": "~2.0.3",
+						"isarray": "~1.0.0",
+						"process-nextick-args": "~2.0.0",
+						"safe-buffer": "~5.1.1",
+						"string_decoder": "~1.1.1",
+						"util-deprecate": "~1.0.1"
+					}
+				},
+				"string_decoder": {
+					"version": "1.1.1",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+					"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+					"dev": true,
+					"requires": {
+						"safe-buffer": "~5.1.0"
+					}
+				}
+			}
+		},
+		"console-browserify": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+			"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+			"dev": true,
+			"requires": {
+				"date-now": "^0.1.4"
+			}
+		},
+		"console-control-strings": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+			"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+			"dev": true
+		},
+		"continuable-cache": {
+			"version": "0.3.1",
+			"resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz",
+			"integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=",
+			"dev": true
+		},
+		"convert-source-map": {
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+			"integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+			"dev": true,
+			"requires": {
+				"safe-buffer": "~5.1.1"
+			}
+		},
+		"copy-descriptor": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+			"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+			"dev": true
+		},
+		"core-util-is": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+			"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+			"dev": true
+		},
+		"cosmiconfig": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
+			"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+			"dev": true,
+			"requires": {
+				"@types/parse-json": "^4.0.0",
+				"import-fresh": "^3.1.0",
+				"parse-json": "^5.0.0",
+				"path-type": "^4.0.0",
+				"yaml": "^1.7.2"
+			},
+			"dependencies": {
+				"parse-json": {
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
+					"integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+					"dev": true,
+					"requires": {
+						"@babel/code-frame": "^7.0.0",
+						"error-ex": "^1.3.1",
+						"json-parse-better-errors": "^1.0.1",
+						"lines-and-columns": "^1.1.6"
+					}
+				},
+				"path-type": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+					"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+					"dev": true
+				}
+			}
+		},
+		"cross-spawn": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+			"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+			"dev": true,
+			"requires": {
+				"lru-cache": "^4.0.1",
+				"which": "^1.2.9"
+			}
+		},
+		"cssesc": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+			"dev": true
+		},
+		"currently-unhandled": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+			"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+			"dev": true,
+			"requires": {
+				"array-find-index": "^1.0.1"
+			}
+		},
+		"dashdash": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+			"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+			"dev": true,
+			"requires": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"date-now": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+			"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+			"dev": true
+		},
+		"dateformat": {
+			"version": "1.0.12",
+			"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
+			"integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
+			"dev": true,
+			"requires": {
+				"get-stdin": "^4.0.1",
+				"meow": "^3.3.0"
+			}
+		},
+		"debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dev": true,
+			"requires": {
+				"ms": "2.0.0"
+			}
+		},
+		"decamelize": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+			"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+			"dev": true
+		},
+		"decamelize-keys": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
+			"integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
+			"dev": true,
+			"requires": {
+				"decamelize": "^1.1.0",
+				"map-obj": "^1.0.0"
+			}
+		},
+		"decode-uri-component": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+			"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+			"dev": true
+		},
+		"define-property": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+			"integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+			"dev": true,
+			"requires": {
+				"is-descriptor": "^1.0.2",
+				"isobject": "^3.0.1"
+			},
+			"dependencies": {
+				"is-accessor-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+					"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-data-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+					"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-descriptor": {
+					"version": "1.0.2",
+					"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+					"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+					"dev": true,
+					"requires": {
+						"is-accessor-descriptor": "^1.0.0",
+						"is-data-descriptor": "^1.0.0",
+						"kind-of": "^6.0.2"
+					}
+				}
+			}
+		},
+		"delayed-stream": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+			"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+			"dev": true
+		},
+		"delegates": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+			"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+			"dev": true
+		},
+		"detect-file": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+			"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+			"dev": true
+		},
+		"diff": {
+			"version": "2.2.3",
+			"resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz",
+			"integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=",
+			"dev": true
+		},
+		"dir-glob": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+			"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+			"dev": true,
+			"requires": {
+				"path-type": "^4.0.0"
+			},
+			"dependencies": {
+				"path-type": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+					"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+					"dev": true
+				}
+			}
+		},
+		"dom-serializer": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
+			"integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
+			"dev": true,
+			"requires": {
+				"domelementtype": "^2.0.1",
+				"entities": "^2.0.0"
+			},
+			"dependencies": {
+				"domelementtype": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+					"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
+					"dev": true
+				},
+				"entities": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
+					"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
+					"dev": true
+				}
+			}
+		},
+		"domelementtype": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+			"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+			"dev": true
+		},
+		"domhandler": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+			"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
+			"dev": true,
+			"requires": {
+				"domelementtype": "1"
+			}
+		},
+		"domutils": {
+			"version": "1.5.1",
+			"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+			"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+			"dev": true,
+			"requires": {
+				"dom-serializer": "0",
+				"domelementtype": "1"
+			}
+		},
+		"duplexer": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+			"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+			"dev": true
+		},
+		"ecc-jsbn": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+			"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+			"dev": true,
+			"requires": {
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.1.0"
+			}
+		},
+		"electron-to-chromium": {
+			"version": "1.3.285",
+			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.285.tgz",
+			"integrity": "sha512-DYR9KW723sUbGK++DCmCmM95AbNXT4Q0tlCFMcYijFjayhuDqlGYR68OemlP8MJj0gjkwdeItIUfd0oLCgw+4A==",
+			"dev": true
+		},
+		"emoji-regex": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+			"dev": true
+		},
+		"encoding": {
+			"version": "0.1.12",
+			"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+			"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+			"dev": true,
+			"requires": {
+				"iconv-lite": "~0.4.13"
+			}
+		},
+		"entities": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+			"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
+			"dev": true
+		},
+		"error": {
+			"version": "7.2.0",
+			"resolved": "https://registry.npmjs.org/error/-/error-7.2.0.tgz",
+			"integrity": "sha512-M6t3j3Vt3uDicrViMP5fLq2AeADNrCVFD8Oj4Qt2MHsX0mPYG7D5XdnEfSdRpaHQzjAJ19wu+I1mw9rQYMTAPg==",
+			"dev": true,
+			"requires": {
+				"string-template": "~0.2.1"
+			}
+		},
+		"error-ex": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+			"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+			"dev": true,
+			"requires": {
+				"is-arrayish": "^0.2.1"
+			}
+		},
+		"escape-string-regexp": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+			"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+			"dev": true
+		},
+		"esprima": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+			"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+			"dev": true
+		},
+		"eventemitter2": {
+			"version": "0.4.14",
+			"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
+			"integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=",
+			"dev": true
+		},
+		"execall": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz",
+			"integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==",
+			"dev": true,
+			"requires": {
+				"clone-regexp": "^2.1.0"
+			}
+		},
+		"exit": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+			"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+			"dev": true
+		},
+		"expand-brackets": {
+			"version": "2.1.4",
+			"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+			"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+			"dev": true,
+			"requires": {
+				"debug": "^2.3.3",
+				"define-property": "^0.2.5",
+				"extend-shallow": "^2.0.1",
+				"posix-character-classes": "^0.1.0",
+				"regex-not": "^1.0.0",
+				"snapdragon": "^0.8.1",
+				"to-regex": "^3.0.1"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "0.2.5",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+					"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^0.1.0"
+					}
+				},
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				}
+			}
+		},
+		"expand-tilde": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+			"integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+			"dev": true,
+			"requires": {
+				"homedir-polyfill": "^1.0.1"
+			}
+		},
+		"extend": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+			"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+			"dev": true
+		},
+		"extend-shallow": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+			"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+			"dev": true,
+			"requires": {
+				"assign-symbols": "^1.0.0",
+				"is-extendable": "^1.0.1"
+			},
+			"dependencies": {
+				"is-extendable": {
+					"version": "1.0.1",
+					"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+					"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+					"dev": true,
+					"requires": {
+						"is-plain-object": "^2.0.4"
+					}
+				}
+			}
+		},
+		"external-editor": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+			"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
+			"dev": true,
+			"requires": {
+				"chardet": "^0.4.0",
+				"iconv-lite": "^0.4.17",
+				"tmp": "^0.0.33"
+			}
+		},
+		"extglob": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+			"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+			"dev": true,
+			"requires": {
+				"array-unique": "^0.3.2",
+				"define-property": "^1.0.0",
+				"expand-brackets": "^2.1.4",
+				"extend-shallow": "^2.0.1",
+				"fragment-cache": "^0.2.1",
+				"regex-not": "^1.0.0",
+				"snapdragon": "^0.8.1",
+				"to-regex": "^3.0.1"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+					"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^1.0.0"
+					}
+				},
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				},
+				"is-accessor-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+					"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-data-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+					"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-descriptor": {
+					"version": "1.0.2",
+					"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+					"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+					"dev": true,
+					"requires": {
+						"is-accessor-descriptor": "^1.0.0",
+						"is-data-descriptor": "^1.0.0",
+						"kind-of": "^6.0.2"
+					}
+				}
+			}
+		},
+		"extsprintf": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+			"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+			"dev": true
+		},
+		"fast-deep-equal": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+			"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+			"dev": true
+		},
+		"fast-glob": {
+			"version": "3.2.2",
+			"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz",
+			"integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==",
+			"dev": true,
+			"requires": {
+				"@nodelib/fs.stat": "^2.0.2",
+				"@nodelib/fs.walk": "^1.2.3",
+				"glob-parent": "^5.1.0",
+				"merge2": "^1.3.0",
+				"micromatch": "^4.0.2",
+				"picomatch": "^2.2.1"
+			},
+			"dependencies": {
+				"braces": {
+					"version": "3.0.2",
+					"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+					"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+					"dev": true,
+					"requires": {
+						"fill-range": "^7.0.1"
+					}
+				},
+				"fill-range": {
+					"version": "7.0.1",
+					"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+					"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+					"dev": true,
+					"requires": {
+						"to-regex-range": "^5.0.1"
+					}
+				},
+				"is-number": {
+					"version": "7.0.0",
+					"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+					"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+					"dev": true
+				},
+				"micromatch": {
+					"version": "4.0.2",
+					"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+					"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+					"dev": true,
+					"requires": {
+						"braces": "^3.0.1",
+						"picomatch": "^2.0.5"
+					}
+				},
+				"to-regex-range": {
+					"version": "5.0.1",
+					"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+					"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+					"dev": true,
+					"requires": {
+						"is-number": "^7.0.0"
+					}
+				}
+			}
+		},
+		"fast-json-stable-stringify": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+			"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+			"dev": true
+		},
+		"fastq": {
+			"version": "1.8.0",
+			"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
+			"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
+			"dev": true,
+			"requires": {
+				"reusify": "^1.0.4"
+			}
+		},
+		"faye-websocket": {
+			"version": "0.10.0",
+			"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+			"integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+			"dev": true,
+			"requires": {
+				"websocket-driver": ">=0.5.1"
+			}
+		},
+		"figures": {
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+			"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+			"dev": true,
+			"requires": {
+				"escape-string-regexp": "^1.0.5",
+				"object-assign": "^4.1.0"
+			}
+		},
+		"file-entry-cache": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+			"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+			"dev": true,
+			"requires": {
+				"flat-cache": "^2.0.1"
+			}
+		},
+		"file-sync-cmp": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz",
+			"integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=",
+			"dev": true
+		},
+		"fill-range": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+			"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+			"dev": true,
+			"requires": {
+				"extend-shallow": "^2.0.1",
+				"is-number": "^3.0.0",
+				"repeat-string": "^1.6.1",
+				"to-regex-range": "^2.1.0"
+			},
+			"dependencies": {
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				}
+			}
+		},
+		"find-up": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+			"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+			"dev": true,
+			"requires": {
+				"path-exists": "^2.0.0",
+				"pinkie-promise": "^2.0.0"
+			}
+		},
+		"findup-sync": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz",
+			"integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=",
+			"dev": true,
+			"requires": {
+				"glob": "~5.0.0"
+			},
+			"dependencies": {
+				"glob": {
+					"version": "5.0.15",
+					"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+					"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+					"dev": true,
+					"requires": {
+						"inflight": "^1.0.4",
+						"inherits": "2",
+						"minimatch": "2 || 3",
+						"once": "^1.3.0",
+						"path-is-absolute": "^1.0.0"
+					}
+				}
+			}
+		},
+		"flat-cache": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+			"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+			"dev": true,
+			"requires": {
+				"flatted": "^2.0.0",
+				"rimraf": "2.6.3",
+				"write": "1.0.3"
+			}
+		},
+		"flatted": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+			"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+			"dev": true
+		},
+		"for-in": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+			"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+			"dev": true
+		},
+		"forever-agent": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+			"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+			"dev": true
+		},
+		"form-data": {
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+			"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+			"dev": true,
+			"requires": {
+				"asynckit": "^0.4.0",
+				"combined-stream": "^1.0.6",
+				"mime-types": "^2.1.12"
+			}
+		},
+		"fragment-cache": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+			"integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+			"dev": true,
+			"requires": {
+				"map-cache": "^0.2.2"
+			}
+		},
+		"fs.realpath": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+			"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+			"dev": true
+		},
+		"fstream": {
+			"version": "1.0.12",
+			"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+			"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+			"dev": true,
+			"requires": {
+				"graceful-fs": "^4.1.2",
+				"inherits": "~2.0.0",
+				"mkdirp": ">=0.5 0",
+				"rimraf": "2"
+			}
+		},
+		"gauge": {
+			"version": "2.7.4",
+			"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+			"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+			"dev": true,
+			"requires": {
+				"aproba": "^1.0.3",
+				"console-control-strings": "^1.0.0",
+				"has-unicode": "^2.0.0",
+				"object-assign": "^4.1.0",
+				"signal-exit": "^3.0.0",
+				"string-width": "^1.0.1",
+				"strip-ansi": "^3.0.1",
+				"wide-align": "^1.1.0"
+			},
+			"dependencies": {
+				"is-fullwidth-code-point": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+					"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+					"dev": true,
+					"requires": {
+						"number-is-nan": "^1.0.0"
+					}
+				},
+				"string-width": {
+					"version": "1.0.2",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+					"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+					"dev": true,
+					"requires": {
+						"code-point-at": "^1.0.0",
+						"is-fullwidth-code-point": "^1.0.0",
+						"strip-ansi": "^3.0.0"
+					}
+				}
+			}
+		},
+		"gaze": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+			"integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+			"dev": true,
+			"requires": {
+				"globule": "^1.0.0"
+			}
+		},
+		"gensync": {
+			"version": "1.0.0-beta.1",
+			"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+			"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+			"dev": true
+		},
+		"get-caller-file": {
+			"version": "2.0.5",
+			"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+			"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+			"dev": true
+		},
+		"get-stdin": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+			"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+			"dev": true
+		},
+		"get-value": {
+			"version": "2.0.6",
+			"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+			"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+			"dev": true
+		},
+		"getobject": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz",
+			"integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=",
+			"dev": true
+		},
+		"getpass": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+			"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+			"dev": true,
+			"requires": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"gettext-parser": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-3.1.1.tgz",
+			"integrity": "sha512-vNhWcqXEtZPs5Ft1ReA34g7ByWotpcOIeJvXVy2jF3/G2U9v6W0wG4Z4hXzcU8R//jArqkgHcVCGgGqa4vxVlQ==",
+			"dev": true,
+			"requires": {
+				"encoding": "^0.1.12",
+				"readable-stream": "^3.2.0",
+				"safe-buffer": "^5.1.2"
+			},
+			"dependencies": {
+				"readable-stream": {
+					"version": "3.4.0",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+					"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+					"dev": true,
+					"requires": {
+						"inherits": "^2.0.3",
+						"string_decoder": "^1.1.1",
+						"util-deprecate": "^1.0.1"
+					}
+				},
+				"string_decoder": {
+					"version": "1.3.0",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+					"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+					"dev": true,
+					"requires": {
+						"safe-buffer": "~5.2.0"
+					},
+					"dependencies": {
+						"safe-buffer": {
+							"version": "5.2.0",
+							"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+							"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
+							"dev": true
+						}
+					}
+				}
+			}
+		},
+		"glob": {
+			"version": "7.0.6",
+			"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
+			"integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
+			"dev": true,
+			"requires": {
+				"fs.realpath": "^1.0.0",
+				"inflight": "^1.0.4",
+				"inherits": "2",
+				"minimatch": "^3.0.2",
+				"once": "^1.3.0",
+				"path-is-absolute": "^1.0.0"
+			}
+		},
+		"glob-parent": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+			"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+			"dev": true,
+			"requires": {
+				"is-glob": "^4.0.1"
+			},
+			"dependencies": {
+				"is-glob": {
+					"version": "4.0.1",
+					"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+					"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+					"dev": true,
+					"requires": {
+						"is-extglob": "^2.1.1"
+					}
+				}
+			}
+		},
+		"global-modules": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+			"integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+			"dev": true,
+			"requires": {
+				"global-prefix": "^1.0.1",
+				"is-windows": "^1.0.1",
+				"resolve-dir": "^1.0.0"
+			}
+		},
+		"global-prefix": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+			"integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+			"dev": true,
+			"requires": {
+				"expand-tilde": "^2.0.2",
+				"homedir-polyfill": "^1.0.1",
+				"ini": "^1.3.4",
+				"is-windows": "^1.0.1",
+				"which": "^1.2.14"
+			}
+		},
+		"globals": {
+			"version": "11.12.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+			"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+			"dev": true
+		},
+		"globby": {
+			"version": "11.0.0",
+			"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz",
+			"integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==",
+			"dev": true,
+			"requires": {
+				"array-union": "^2.1.0",
+				"dir-glob": "^3.0.1",
+				"fast-glob": "^3.1.1",
+				"ignore": "^5.1.4",
+				"merge2": "^1.3.0",
+				"slash": "^3.0.0"
+			}
+		},
+		"globjoin": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz",
+			"integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=",
+			"dev": true
+		},
+		"globule": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
+			"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+			"dev": true,
+			"requires": {
+				"glob": "~7.1.1",
+				"lodash": "~4.17.10",
+				"minimatch": "~3.0.2"
+			},
+			"dependencies": {
+				"glob": {
+					"version": "7.1.4",
+					"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+					"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+					"dev": true,
+					"requires": {
+						"fs.realpath": "^1.0.0",
+						"inflight": "^1.0.4",
+						"inherits": "2",
+						"minimatch": "^3.0.4",
+						"once": "^1.3.0",
+						"path-is-absolute": "^1.0.0"
+					}
+				}
+			}
+		},
+		"gonzales-pe": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
+			"integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
+			"dev": true,
+			"requires": {
+				"minimist": "^1.2.5"
+			}
+		},
+		"graceful-fs": {
+			"version": "4.2.2",
+			"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
+			"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
+			"dev": true
+		},
+		"grunt": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/grunt/-/grunt-1.1.0.tgz",
+			"integrity": "sha512-+NGod0grmviZ7Nzdi9am7vuRS/h76PcWDsV635mEXF0PEQMUV6Kb+OjTdsVxbi0PZmfQOjCMKb3w8CVZcqsn1g==",
+			"dev": true,
+			"requires": {
+				"coffeescript": "~1.10.0",
+				"dateformat": "~1.0.12",
+				"eventemitter2": "~0.4.13",
+				"exit": "~0.1.1",
+				"findup-sync": "~0.3.0",
+				"glob": "~7.0.0",
+				"grunt-cli": "~1.2.0",
+				"grunt-known-options": "~1.1.0",
+				"grunt-legacy-log": "~2.0.0",
+				"grunt-legacy-util": "~1.1.1",
+				"iconv-lite": "~0.4.13",
+				"js-yaml": "~3.13.1",
+				"minimatch": "~3.0.2",
+				"mkdirp": "~1.0.3",
+				"nopt": "~3.0.6",
+				"path-is-absolute": "~1.0.0",
+				"rimraf": "~2.6.2"
+			},
+			"dependencies": {
+				"grunt-cli": {
+					"version": "1.2.0",
+					"resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz",
+					"integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=",
+					"dev": true,
+					"requires": {
+						"findup-sync": "~0.3.0",
+						"grunt-known-options": "~1.1.0",
+						"nopt": "~3.0.6",
+						"resolve": "~1.1.0"
+					}
+				},
+				"mkdirp": {
+					"version": "1.0.3",
+					"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz",
+					"integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==",
+					"dev": true
+				},
+				"resolve": {
+					"version": "1.1.7",
+					"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+					"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-check-dependencies": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/grunt-check-dependencies/-/grunt-check-dependencies-1.0.0.tgz",
+			"integrity": "sha1-UYiVh8V+gn3enN9pt1CuCy+IHFA=",
+			"dev": true,
+			"requires": {
+				"check-dependencies": "^1.0.1",
+				"lodash.clonedeep": "^4.5.0"
+			}
+		},
+		"grunt-checktextdomain": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/grunt-checktextdomain/-/grunt-checktextdomain-1.0.1.tgz",
+			"integrity": "sha1-slTQHh3pEwBdTbHFMD2QI7mD4Zs=",
+			"dev": true,
+			"requires": {
+				"chalk": "~0.2.1",
+				"text-table": "~0.2.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "0.2.0",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz",
+					"integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "0.2.1",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-0.2.1.tgz",
+					"integrity": "sha1-dhPhV1FFshOGSD9/SFql/6jL0Qw=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "~0.2.0",
+						"has-color": "~0.1.0"
+					}
+				}
+			}
+		},
+		"grunt-contrib-clean": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz",
+			"integrity": "sha1-Vkq/LQN4qYOhW54/MO51tzjEBjg=",
+			"dev": true,
+			"requires": {
+				"async": "^1.5.2",
+				"rimraf": "^2.5.1"
+			}
+		},
+		"grunt-contrib-copy": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz",
+			"integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.1.1",
+				"file-sync-cmp": "^0.1.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-contrib-cssmin": {
+			"version": "2.2.1",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.1.tgz",
+			"integrity": "sha512-IXNomhQ5ekVZbDbj/ik5YccoD9khU6LT2fDXqO1+/Txjq8cp0tQKjVS8i8EAbHOrSDkL7/UD6A7b+xj98gqh9w==",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.0.0",
+				"clean-css": "~4.1.1",
+				"maxmin": "^2.1.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-contrib-jshint": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz",
+			"integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.1.1",
+				"hooker": "^0.2.3",
+				"jshint": "~2.9.4"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-contrib-uglify": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-3.0.1.tgz",
+			"integrity": "sha1-/etfk4pMgEL46Grkb2NVTo6VEcs=",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.0.0",
+				"maxmin": "^1.1.0",
+				"uglify-js": "~3.0.4",
+				"uri-path": "^1.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"gzip-size": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz",
+					"integrity": "sha1-Zs+LEBBHInuVus5uodoMF37Vwi8=",
+					"dev": true,
+					"requires": {
+						"browserify-zlib": "^0.1.4",
+						"concat-stream": "^1.4.1"
+					}
+				},
+				"maxmin": {
+					"version": "1.1.0",
+					"resolved": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz",
+					"integrity": "sha1-cTZehKmd2Piz99X94vANHn9zvmE=",
+					"dev": true,
+					"requires": {
+						"chalk": "^1.0.0",
+						"figures": "^1.0.1",
+						"gzip-size": "^1.0.0",
+						"pretty-bytes": "^1.0.0"
+					}
+				},
+				"pretty-bytes": {
+					"version": "1.0.4",
+					"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
+					"integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=",
+					"dev": true,
+					"requires": {
+						"get-stdin": "^4.0.1",
+						"meow": "^3.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-contrib-watch": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz",
+			"integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==",
+			"dev": true,
+			"requires": {
+				"async": "^2.6.0",
+				"gaze": "^1.1.0",
+				"lodash": "^4.17.10",
+				"tiny-lr": "^1.1.1"
+			},
+			"dependencies": {
+				"async": {
+					"version": "2.6.3",
+					"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+					"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+					"dev": true,
+					"requires": {
+						"lodash": "^4.17.14"
+					}
+				},
+				"lodash": {
+					"version": "4.17.20",
+					"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+					"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+					"dev": true
+				}
+			}
+		},
+		"grunt-exec": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/grunt-exec/-/grunt-exec-2.0.0.tgz",
+			"integrity": "sha1-pXWmILHaRBbCksAd9WTJKWuAqyM=",
+			"dev": true
+		},
+		"grunt-jsvalidate": {
+			"version": "0.2.2",
+			"resolved": "https://registry.npmjs.org/grunt-jsvalidate/-/grunt-jsvalidate-0.2.2.tgz",
+			"integrity": "sha1-/QlEJYiNbmPfqgbPsJ7gUrjrvo8=",
+			"dev": true,
+			"requires": {
+				"esprima": "~1.0.0"
+			},
+			"dependencies": {
+				"esprima": {
+					"version": "1.0.4",
+					"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz",
+					"integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-known-options": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz",
+			"integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==",
+			"dev": true
+		},
+		"grunt-legacy-log": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz",
+			"integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==",
+			"dev": true,
+			"requires": {
+				"colors": "~1.1.2",
+				"grunt-legacy-log-utils": "~2.0.0",
+				"hooker": "~0.2.3",
+				"lodash": "~4.17.5"
+			}
+		},
+		"grunt-legacy-log-utils": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz",
+			"integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==",
+			"dev": true,
+			"requires": {
+				"chalk": "~2.4.1",
+				"lodash": "~4.17.10"
+			}
+		},
+		"grunt-legacy-util": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz",
+			"integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==",
+			"dev": true,
+			"requires": {
+				"async": "~1.5.2",
+				"exit": "~0.1.1",
+				"getobject": "~0.1.0",
+				"hooker": "~0.2.3",
+				"lodash": "~4.17.10",
+				"underscore.string": "~3.3.4",
+				"which": "~1.3.0"
+			}
+		},
+		"grunt-patch-wordpress": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/grunt-patch-wordpress/-/grunt-patch-wordpress-1.0.0.tgz",
+			"integrity": "sha512-2wvtOqxMhiu7QCujEAvFFvLfUYec0fKDvFRnwKUSHizl5eH8cgXv6t0yc7W4yEeito/u8v8r8fzDHRhXLkIXzA==",
+			"dev": true,
+			"requires": {
+				"grunt": ">=0.4.5",
+				"inquirer": "^5.1.0",
+				"request": "^2.83.0",
+				"underscore": "~1.8.3",
+				"underscore.string": "~3.3.4",
+				"xmlrpc": "^1.3.1"
+			}
+		},
+		"grunt-postcss": {
+			"version": "0.8.0",
+			"resolved": "https://registry.npmjs.org/grunt-postcss/-/grunt-postcss-0.8.0.tgz",
+			"integrity": "sha1-jzCor2B5A84MRfAfC+QsYOMc6w4=",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.0.0",
+				"diff": "^2.0.2",
+				"postcss": "^5.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"has-flag": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+					"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+					"dev": true
+				},
+				"postcss": {
+					"version": "5.2.18",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
+					"integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
+					"dev": true,
+					"requires": {
+						"chalk": "^1.1.3",
+						"js-base64": "^2.1.9",
+						"source-map": "^0.5.6",
+						"supports-color": "^3.2.3"
+					},
+					"dependencies": {
+						"supports-color": {
+							"version": "3.2.3",
+							"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+							"integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+							"dev": true,
+							"requires": {
+								"has-flag": "^1.0.0"
+							}
+						}
+					}
+				},
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-rtlcss": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/grunt-rtlcss/-/grunt-rtlcss-2.0.2.tgz",
+			"integrity": "sha512-WbI2thnwlF04N+xvJu+NxqEaCyPuLyar196SYhEQFZ2EJHkOS8YYE+Zkh+X9cWhwAtKp7ZEpR/IKXcyQggOIsQ==",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.0.0",
+				"rtlcss": "^2.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-sass": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/grunt-sass/-/grunt-sass-3.0.2.tgz",
+			"integrity": "sha512-Ogq4cWqBre71gZIkgxIxevgzZHSIIsrKu/5yvPDl4Mvib0A4TRTJEQUdpQ0YV1iai0DPjayz02vDJE6KUVHQ2w==",
+			"dev": true
+		},
+		"grunt-stylelint": {
+			"version": "0.8.0",
+			"resolved": "https://registry.npmjs.org/grunt-stylelint/-/grunt-stylelint-0.8.0.tgz",
+			"integrity": "sha1-cAtdw9X3yY/2MvZXYBWqjFZnzlI=",
+			"dev": true,
+			"requires": {
+				"chalk": "1.1.3"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"grunt-wp-i18n": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/grunt-wp-i18n/-/grunt-wp-i18n-1.0.3.tgz",
+			"integrity": "sha512-CJNbEKeBeOSAPeaJ9B8iCgSwtaG63UR9/uT46a4OsIqnFhOJpeAi138JTlvjfIbnDVoBrzvdrKJe1svveLjUtA==",
+			"dev": true,
+			"requires": {
+				"grunt": "^1.0.3",
+				"node-wp-i18n": "^1.2.2"
+			}
+		},
+		"gzip-size": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
+			"integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=",
+			"dev": true,
+			"requires": {
+				"duplexer": "^0.1.1"
+			}
+		},
+		"har-schema": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+			"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+			"dev": true
+		},
+		"har-validator": {
+			"version": "5.1.3",
+			"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+			"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+			"dev": true,
+			"requires": {
+				"ajv": "^6.5.5",
+				"har-schema": "^2.0.0"
+			}
+		},
+		"hard-rejection": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+			"integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+			"dev": true
+		},
+		"has-ansi": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+			"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+			"dev": true,
+			"requires": {
+				"ansi-regex": "^2.0.0"
+			}
+		},
+		"has-color": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
+			"integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
+			"dev": true
+		},
+		"has-flag": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+			"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+			"dev": true
+		},
+		"has-unicode": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+			"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+			"dev": true
+		},
+		"has-value": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+			"integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+			"dev": true,
+			"requires": {
+				"get-value": "^2.0.6",
+				"has-values": "^1.0.0",
+				"isobject": "^3.0.0"
+			}
+		},
+		"has-values": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+			"integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+			"dev": true,
+			"requires": {
+				"is-number": "^3.0.0",
+				"kind-of": "^4.0.0"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+					"integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"homedir-polyfill": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+			"integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+			"dev": true,
+			"requires": {
+				"parse-passwd": "^1.0.0"
+			}
+		},
+		"hooker": {
+			"version": "0.2.3",
+			"resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz",
+			"integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=",
+			"dev": true
+		},
+		"hosted-git-info": {
+			"version": "2.8.5",
+			"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
+			"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
+			"dev": true
+		},
+		"html-tags": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
+			"integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
+			"dev": true
+		},
+		"htmlparser2": {
+			"version": "3.8.3",
+			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+			"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
+			"dev": true,
+			"requires": {
+				"domelementtype": "1",
+				"domhandler": "2.3",
+				"domutils": "1.5",
+				"entities": "1.0",
+				"readable-stream": "1.1"
+			}
+		},
+		"http-parser-js": {
+			"version": "0.4.10",
+			"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+			"integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
+			"dev": true
+		},
+		"http-signature": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+			"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+			"dev": true,
+			"requires": {
+				"assert-plus": "^1.0.0",
+				"jsprim": "^1.2.2",
+				"sshpk": "^1.7.0"
+			}
+		},
+		"iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dev": true,
+			"requires": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			}
+		},
+		"ignore": {
+			"version": "5.1.6",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz",
+			"integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==",
+			"dev": true
+		},
+		"import-fresh": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+			"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+			"dev": true,
+			"requires": {
+				"parent-module": "^1.0.0",
+				"resolve-from": "^4.0.0"
+			},
+			"dependencies": {
+				"resolve-from": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+					"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+					"dev": true
+				}
+			}
+		},
+		"import-lazy": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
+			"integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
+			"dev": true
+		},
+		"imurmurhash": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+			"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+			"dev": true
+		},
+		"in-publish": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
+			"integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
+			"dev": true
+		},
+		"indent-string": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+			"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+			"dev": true,
+			"requires": {
+				"repeating": "^2.0.0"
+			}
+		},
+		"indexes-of": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+			"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+			"dev": true
+		},
+		"inflight": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+			"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+			"dev": true,
+			"requires": {
+				"once": "^1.3.0",
+				"wrappy": "1"
+			}
+		},
+		"inherits": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+			"dev": true
+		},
+		"ini": {
+			"version": "1.3.5",
+			"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+			"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+			"dev": true
+		},
+		"inquirer": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
+			"integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==",
+			"dev": true,
+			"requires": {
+				"ansi-escapes": "^3.0.0",
+				"chalk": "^2.0.0",
+				"cli-cursor": "^2.1.0",
+				"cli-width": "^2.0.0",
+				"external-editor": "^2.1.0",
+				"figures": "^2.0.0",
+				"lodash": "^4.3.0",
+				"mute-stream": "0.0.7",
+				"run-async": "^2.2.0",
+				"rxjs": "^5.5.2",
+				"string-width": "^2.1.0",
+				"strip-ansi": "^4.0.0",
+				"through": "^2.3.6"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+					"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+					"dev": true
+				},
+				"figures": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+					"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+					"dev": true,
+					"requires": {
+						"escape-string-regexp": "^1.0.5"
+					}
+				},
+				"strip-ansi": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+					"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^3.0.0"
+					}
+				}
+			}
+		},
+		"is-accessor-descriptor": {
+			"version": "0.1.6",
+			"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+			"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+			"dev": true,
+			"requires": {
+				"kind-of": "^3.0.2"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"is-alphabetical": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+			"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+			"dev": true
+		},
+		"is-alphanumeric": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz",
+			"integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=",
+			"dev": true
+		},
+		"is-alphanumerical": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+			"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+			"dev": true,
+			"requires": {
+				"is-alphabetical": "^1.0.0",
+				"is-decimal": "^1.0.0"
+			}
+		},
+		"is-arrayish": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+			"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+			"dev": true
+		},
+		"is-buffer": {
+			"version": "1.1.6",
+			"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+			"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+			"dev": true
+		},
+		"is-data-descriptor": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+			"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+			"dev": true,
+			"requires": {
+				"kind-of": "^3.0.2"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"is-decimal": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+			"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+			"dev": true
+		},
+		"is-descriptor": {
+			"version": "0.1.6",
+			"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+			"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+			"dev": true,
+			"requires": {
+				"is-accessor-descriptor": "^0.1.6",
+				"is-data-descriptor": "^0.1.4",
+				"kind-of": "^5.0.0"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "5.1.0",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+					"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+					"dev": true
+				}
+			}
+		},
+		"is-extendable": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+			"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+			"dev": true
+		},
+		"is-extglob": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+			"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+			"dev": true
+		},
+		"is-finite": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+			"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+			"dev": true,
+			"requires": {
+				"number-is-nan": "^1.0.0"
+			}
+		},
+		"is-fullwidth-code-point": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+			"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+			"dev": true
+		},
+		"is-glob": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+			"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+			"dev": true,
+			"requires": {
+				"is-extglob": "^2.1.0"
+			}
+		},
+		"is-hexadecimal": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+			"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+			"dev": true
+		},
+		"is-number": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+			"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+			"dev": true,
+			"requires": {
+				"kind-of": "^3.0.2"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"is-plain-obj": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+			"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+			"dev": true
+		},
+		"is-plain-object": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+			"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+			"dev": true,
+			"requires": {
+				"isobject": "^3.0.1"
+			}
+		},
+		"is-promise": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+			"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+			"dev": true
+		},
+		"is-regexp": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz",
+			"integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==",
+			"dev": true
+		},
+		"is-typedarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+			"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+			"dev": true
+		},
+		"is-utf8": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+			"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+			"dev": true
+		},
+		"is-whitespace-character": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
+			"integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==",
+			"dev": true
+		},
+		"is-windows": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+			"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+			"dev": true
+		},
+		"is-word-character": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz",
+			"integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==",
+			"dev": true
+		},
+		"isarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+			"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+			"dev": true
+		},
+		"isexe": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+			"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+			"dev": true
+		},
+		"isobject": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+			"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+			"dev": true
+		},
+		"isstream": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+			"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+			"dev": true
+		},
+		"js-base64": {
+			"version": "2.5.1",
+			"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
+			"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
+			"dev": true
+		},
+		"js-tokens": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+			"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+			"dev": true
+		},
+		"js-yaml": {
+			"version": "3.13.1",
+			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+			"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+			"dev": true,
+			"requires": {
+				"argparse": "^1.0.7",
+				"esprima": "^4.0.0"
+			}
+		},
+		"jsbn": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+			"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+			"dev": true
+		},
+		"jsesc": {
+			"version": "2.5.2",
+			"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+			"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+			"dev": true
+		},
+		"jshint": {
+			"version": "2.9.7",
+			"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.7.tgz",
+			"integrity": "sha512-Q8XN38hGsVQhdlM+4gd1Xl7OB1VieSuCJf+fEJjpo59JH99bVJhXRXAh26qQ15wfdd1VPMuDWNeSWoNl53T4YA==",
+			"dev": true,
+			"requires": {
+				"cli": "~1.0.0",
+				"console-browserify": "1.1.x",
+				"exit": "0.1.x",
+				"htmlparser2": "3.8.x",
+				"lodash": "~4.17.10",
+				"minimatch": "~3.0.2",
+				"shelljs": "0.3.x",
+				"strip-json-comments": "1.0.x"
+			}
+		},
+		"json-parse-better-errors": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+			"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+			"dev": true
+		},
+		"json-schema": {
+			"version": "0.2.3",
+			"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+			"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+			"dev": true
+		},
+		"json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+			"dev": true
+		},
+		"json-stringify-safe": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+			"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+			"dev": true
+		},
+		"json5": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+			"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+			"dev": true,
+			"requires": {
+				"minimist": "^1.2.5"
+			}
+		},
+		"jsprim": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+			"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+			"dev": true,
+			"requires": {
+				"assert-plus": "1.0.0",
+				"extsprintf": "1.3.0",
+				"json-schema": "0.2.3",
+				"verror": "1.10.0"
+			}
+		},
+		"kind-of": {
+			"version": "6.0.3",
+			"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+			"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+			"dev": true
+		},
+		"known-css-properties": {
+			"version": "0.19.0",
+			"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.19.0.tgz",
+			"integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==",
+			"dev": true
+		},
+		"leven": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+			"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+			"dev": true
+		},
+		"lines-and-columns": {
+			"version": "1.1.6",
+			"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
+			"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
+			"dev": true
+		},
+		"livereload-js": {
+			"version": "2.4.0",
+			"resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz",
+			"integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==",
+			"dev": true
+		},
+		"load-json-file": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+			"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+			"dev": true,
+			"requires": {
+				"graceful-fs": "^4.1.2",
+				"parse-json": "^2.2.0",
+				"pify": "^2.0.0",
+				"pinkie-promise": "^2.0.0",
+				"strip-bom": "^2.0.0"
+			}
+		},
+		"locate-path": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+			"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+			"dev": true,
+			"requires": {
+				"p-locate": "^2.0.0",
+				"path-exists": "^3.0.0"
+			},
+			"dependencies": {
+				"path-exists": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+					"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+					"dev": true
+				}
+			}
+		},
+		"lodash": {
+			"version": "4.17.20",
+			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+			"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+			"dev": true
+		},
+		"lodash.camelcase": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+			"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+			"dev": true
+		},
+		"lodash.clonedeep": {
+			"version": "4.5.0",
+			"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+			"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+			"dev": true
+		},
+		"log-symbols": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+			"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+			"dev": true,
+			"requires": {
+				"chalk": "^4.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "4.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+					"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+					"dev": true,
+					"requires": {
+						"@types/color-name": "^1.1.1",
+						"color-convert": "^2.0.1"
+					}
+				},
+				"chalk": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+					"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^4.1.0",
+						"supports-color": "^7.1.0"
+					}
+				},
+				"color-convert": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+					"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+					"dev": true,
+					"requires": {
+						"color-name": "~1.1.4"
+					}
+				},
+				"color-name": {
+					"version": "1.1.4",
+					"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+					"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+					"dev": true
+				},
+				"has-flag": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+					"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+					"dev": true
+				},
+				"supports-color": {
+					"version": "7.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+					"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^4.0.0"
+					}
+				}
+			}
+		},
+		"longest-streak": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz",
+			"integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==",
+			"dev": true
+		},
+		"loud-rejection": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+			"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+			"dev": true,
+			"requires": {
+				"currently-unhandled": "^0.4.1",
+				"signal-exit": "^3.0.0"
+			}
+		},
+		"lru-cache": {
+			"version": "4.1.5",
+			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+			"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+			"dev": true,
+			"requires": {
+				"pseudomap": "^1.0.2",
+				"yallist": "^2.1.2"
+			}
+		},
+		"map-cache": {
+			"version": "0.2.2",
+			"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+			"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+			"dev": true
+		},
+		"map-obj": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+			"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+			"dev": true
+		},
+		"map-visit": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+			"integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+			"dev": true,
+			"requires": {
+				"object-visit": "^1.0.0"
+			}
+		},
+		"markdown-escapes": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
+			"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==",
+			"dev": true
+		},
+		"markdown-table": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
+			"integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
+			"dev": true,
+			"requires": {
+				"repeat-string": "^1.0.0"
+			}
+		},
+		"matchdep": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
+			"integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=",
+			"dev": true,
+			"requires": {
+				"findup-sync": "^2.0.0",
+				"micromatch": "^3.0.4",
+				"resolve": "^1.4.0",
+				"stack-trace": "0.0.10"
+			},
+			"dependencies": {
+				"findup-sync": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
+					"integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
+					"dev": true,
+					"requires": {
+						"detect-file": "^1.0.0",
+						"is-glob": "^3.1.0",
+						"micromatch": "^3.0.4",
+						"resolve-dir": "^1.0.1"
+					}
+				}
+			}
+		},
+		"mathml-tag-names": {
+			"version": "2.1.3",
+			"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
+			"integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==",
+			"dev": true
+		},
+		"maxmin": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz",
+			"integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=",
+			"dev": true,
+			"requires": {
+				"chalk": "^1.0.0",
+				"figures": "^1.0.1",
+				"gzip-size": "^3.0.0",
+				"pretty-bytes": "^3.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"mdast-util-compact": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz",
+			"integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==",
+			"dev": true,
+			"requires": {
+				"unist-util-visit": "^2.0.0"
+			}
+		},
+		"meow": {
+			"version": "3.7.0",
+			"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+			"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+			"dev": true,
+			"requires": {
+				"camelcase-keys": "^2.0.0",
+				"decamelize": "^1.1.2",
+				"loud-rejection": "^1.0.0",
+				"map-obj": "^1.0.1",
+				"minimist": "^1.1.3",
+				"normalize-package-data": "^2.3.4",
+				"object-assign": "^4.0.1",
+				"read-pkg-up": "^1.0.1",
+				"redent": "^1.0.0",
+				"trim-newlines": "^1.0.0"
+			}
+		},
+		"merge2": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
+			"integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
+			"dev": true
+		},
+		"micromatch": {
+			"version": "3.1.10",
+			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+			"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+			"dev": true,
+			"requires": {
+				"arr-diff": "^4.0.0",
+				"array-unique": "^0.3.2",
+				"braces": "^2.3.1",
+				"define-property": "^2.0.2",
+				"extend-shallow": "^3.0.2",
+				"extglob": "^2.0.4",
+				"fragment-cache": "^0.2.1",
+				"kind-of": "^6.0.2",
+				"nanomatch": "^1.2.9",
+				"object.pick": "^1.3.0",
+				"regex-not": "^1.0.0",
+				"snapdragon": "^0.8.1",
+				"to-regex": "^3.0.2"
+			}
+		},
+		"mime-db": {
+			"version": "1.40.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+			"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+			"dev": true
+		},
+		"mime-types": {
+			"version": "2.1.24",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+			"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+			"dev": true,
+			"requires": {
+				"mime-db": "1.40.0"
+			}
+		},
+		"mimic-fn": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+			"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+			"dev": true
+		},
+		"min-indent": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
+			"integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=",
+			"dev": true
+		},
+		"minimatch": {
+			"version": "3.0.4",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+			"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+			"dev": true,
+			"requires": {
+				"brace-expansion": "^1.1.7"
+			}
+		},
+		"minimist": {
+			"version": "1.2.5",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+			"dev": true
+		},
+		"minimist-options": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+			"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+			"dev": true,
+			"requires": {
+				"arrify": "^1.0.1",
+				"is-plain-obj": "^1.1.0",
+				"kind-of": "^6.0.3"
+			},
+			"dependencies": {
+				"arrify": {
+					"version": "1.0.1",
+					"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+					"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+					"dev": true
+				},
+				"is-plain-obj": {
+					"version": "1.1.0",
+					"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+					"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+					"dev": true
+				}
+			}
+		},
+		"mixin-deep": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+			"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+			"dev": true,
+			"requires": {
+				"for-in": "^1.0.2",
+				"is-extendable": "^1.0.1"
+			},
+			"dependencies": {
+				"is-extendable": {
+					"version": "1.0.1",
+					"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+					"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+					"dev": true,
+					"requires": {
+						"is-plain-object": "^2.0.4"
+					}
+				}
+			}
+		},
+		"mkdirp": {
+			"version": "0.5.5",
+			"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+			"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+			"dev": true,
+			"requires": {
+				"minimist": "^1.2.5"
+			}
+		},
+		"mout": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/mout/-/mout-1.1.0.tgz",
+			"integrity": "sha512-XsP0vf4As6BfqglxZqbqQ8SR6KQot2AgxvR0gG+WtUkf90vUXchMOZQtPf/Hml1rEffJupqL/tIrU6EYhsUQjw==",
+			"dev": true
+		},
+		"ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+			"dev": true
+		},
+		"mute-stream": {
+			"version": "0.0.7",
+			"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+			"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+			"dev": true
+		},
+		"nan": {
+			"version": "2.14.1",
+			"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+			"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
+			"dev": true
+		},
+		"nanomatch": {
+			"version": "1.2.13",
+			"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+			"integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+			"dev": true,
+			"requires": {
+				"arr-diff": "^4.0.0",
+				"array-unique": "^0.3.2",
+				"define-property": "^2.0.2",
+				"extend-shallow": "^3.0.2",
+				"fragment-cache": "^0.2.1",
+				"is-windows": "^1.0.2",
+				"kind-of": "^6.0.2",
+				"object.pick": "^1.3.0",
+				"regex-not": "^1.0.0",
+				"snapdragon": "^0.8.1",
+				"to-regex": "^3.0.1"
+			}
+		},
+		"node-gyp": {
+			"version": "3.8.0",
+			"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+			"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
+			"dev": true,
+			"requires": {
+				"fstream": "^1.0.0",
+				"glob": "^7.0.3",
+				"graceful-fs": "^4.1.2",
+				"mkdirp": "^0.5.0",
+				"nopt": "2 || 3",
+				"npmlog": "0 || 1 || 2 || 3 || 4",
+				"osenv": "0",
+				"request": "^2.87.0",
+				"rimraf": "2",
+				"semver": "~5.3.0",
+				"tar": "^2.0.0",
+				"which": "1"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "5.3.0",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+					"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+					"dev": true
+				}
+			}
+		},
+		"node-releases": {
+			"version": "1.1.57",
+			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.57.tgz",
+			"integrity": "sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw==",
+			"dev": true
+		},
+		"node-sass": {
+			"version": "4.14.1",
+			"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz",
+			"integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==",
+			"dev": true,
+			"requires": {
+				"async-foreach": "^0.1.3",
+				"chalk": "^1.1.1",
+				"cross-spawn": "^3.0.0",
+				"gaze": "^1.0.0",
+				"get-stdin": "^4.0.1",
+				"glob": "^7.0.3",
+				"in-publish": "^2.0.0",
+				"lodash": "^4.17.15",
+				"meow": "^3.7.0",
+				"mkdirp": "^0.5.1",
+				"nan": "^2.13.2",
+				"node-gyp": "^3.8.0",
+				"npmlog": "^4.0.0",
+				"request": "^2.88.0",
+				"sass-graph": "2.2.5",
+				"stdout-stream": "^1.4.0",
+				"true-case-path": "^1.0.2"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "2.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+					"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+					"dev": true
+				},
+				"chalk": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+					"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^2.2.1",
+						"escape-string-regexp": "^1.0.2",
+						"has-ansi": "^2.0.0",
+						"strip-ansi": "^3.0.0",
+						"supports-color": "^2.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+					"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+					"dev": true
+				}
+			}
+		},
+		"node-wp-i18n": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/node-wp-i18n/-/node-wp-i18n-1.2.3.tgz",
+			"integrity": "sha512-YMzMcsjXbGYDB9vHyb289CYXAGmXhcNLbeTlOnWgPNkZd9xrovcbRd7cQyKd9BQHOjS7Nw8WCbJ7nvtR7rc0rg==",
+			"dev": true,
+			"requires": {
+				"bluebird": "^3.4.1",
+				"gettext-parser": "^3.1.0",
+				"glob": "^7.0.5",
+				"lodash": "^4.14.2",
+				"minimist": "^1.2.0",
+				"mkdirp": "^0.5.1",
+				"tmp": "^0.0.33"
+			}
+		},
+		"nopt": {
+			"version": "3.0.6",
+			"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+			"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+			"dev": true,
+			"requires": {
+				"abbrev": "1"
+			}
+		},
+		"normalize-package-data": {
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+			"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+			"dev": true,
+			"requires": {
+				"hosted-git-info": "^2.1.4",
+				"resolve": "^1.10.0",
+				"semver": "2 || 3 || 4 || 5",
+				"validate-npm-package-license": "^3.0.1"
+			}
+		},
+		"normalize-range": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+			"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+			"dev": true
+		},
+		"normalize-selector": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz",
+			"integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=",
+			"dev": true
+		},
+		"npmlog": {
+			"version": "4.1.2",
+			"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+			"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+			"dev": true,
+			"requires": {
+				"are-we-there-yet": "~1.1.2",
+				"console-control-strings": "~1.1.0",
+				"gauge": "~2.7.3",
+				"set-blocking": "~2.0.0"
+			}
+		},
+		"num2fraction": {
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+			"integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+			"dev": true
+		},
+		"number-is-nan": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+			"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+			"dev": true
+		},
+		"oauth-sign": {
+			"version": "0.9.0",
+			"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+			"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+			"dev": true
+		},
+		"object-assign": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+			"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+			"dev": true
+		},
+		"object-copy": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+			"integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+			"dev": true,
+			"requires": {
+				"copy-descriptor": "^0.1.0",
+				"define-property": "^0.2.5",
+				"kind-of": "^3.0.3"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "0.2.5",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+					"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^0.1.0"
+					}
+				},
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"object-visit": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+			"integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+			"dev": true,
+			"requires": {
+				"isobject": "^3.0.0"
+			}
+		},
+		"object.pick": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+			"integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+			"dev": true,
+			"requires": {
+				"isobject": "^3.0.1"
+			}
+		},
+		"once": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+			"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+			"dev": true,
+			"requires": {
+				"wrappy": "1"
+			}
+		},
+		"onetime": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+			"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+			"dev": true,
+			"requires": {
+				"mimic-fn": "^1.0.0"
+			}
+		},
+		"os-homedir": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+			"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+			"dev": true
+		},
+		"os-tmpdir": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+			"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+			"dev": true
+		},
+		"osenv": {
+			"version": "0.1.5",
+			"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+			"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+			"dev": true,
+			"requires": {
+				"os-homedir": "^1.0.0",
+				"os-tmpdir": "^1.0.0"
+			}
+		},
+		"p-limit": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+			"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+			"dev": true,
+			"requires": {
+				"p-try": "^1.0.0"
+			}
+		},
+		"p-locate": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+			"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+			"dev": true,
+			"requires": {
+				"p-limit": "^1.1.0"
+			}
+		},
+		"p-try": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+			"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+			"dev": true
+		},
+		"pako": {
+			"version": "0.2.9",
+			"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+			"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=",
+			"dev": true
+		},
+		"parent-module": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+			"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+			"dev": true,
+			"requires": {
+				"callsites": "^3.0.0"
+			}
+		},
+		"parse-entities": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+			"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+			"dev": true,
+			"requires": {
+				"character-entities": "^1.0.0",
+				"character-entities-legacy": "^1.0.0",
+				"character-reference-invalid": "^1.0.0",
+				"is-alphanumerical": "^1.0.0",
+				"is-decimal": "^1.0.0",
+				"is-hexadecimal": "^1.0.0"
+			}
+		},
+		"parse-json": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+			"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+			"dev": true,
+			"requires": {
+				"error-ex": "^1.2.0"
+			}
+		},
+		"parse-passwd": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+			"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+			"dev": true
+		},
+		"pascalcase": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+			"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+			"dev": true
+		},
+		"path-exists": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+			"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+			"dev": true,
+			"requires": {
+				"pinkie-promise": "^2.0.0"
+			}
+		},
+		"path-is-absolute": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+			"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+			"dev": true
+		},
+		"path-parse": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+			"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+			"dev": true
+		},
+		"path-type": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+			"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+			"dev": true,
+			"requires": {
+				"graceful-fs": "^4.1.2",
+				"pify": "^2.0.0",
+				"pinkie-promise": "^2.0.0"
+			}
+		},
+		"performance-now": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+			"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+			"dev": true
+		},
+		"picomatch": {
+			"version": "2.2.2",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+			"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+			"dev": true
+		},
+		"pify": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+			"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+			"dev": true
+		},
+		"pinkie": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+			"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+			"dev": true
+		},
+		"pinkie-promise": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+			"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+			"dev": true,
+			"requires": {
+				"pinkie": "^2.0.0"
+			}
+		},
+		"pkg-up": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
+			"integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
+			"dev": true,
+			"requires": {
+				"find-up": "^2.1.0"
+			},
+			"dependencies": {
+				"find-up": {
+					"version": "2.1.0",
+					"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+					"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+					"dev": true,
+					"requires": {
+						"locate-path": "^2.0.0"
+					}
+				}
+			}
+		},
+		"posix-character-classes": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+			"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+			"dev": true
+		},
+		"postcss": {
+			"version": "6.0.23",
+			"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
+			"integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
+			"dev": true,
+			"requires": {
+				"chalk": "^2.4.1",
+				"source-map": "^0.6.1",
+				"supports-color": "^5.4.0"
+			}
+		},
+		"postcss-html": {
+			"version": "0.36.0",
+			"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz",
+			"integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==",
+			"dev": true,
+			"requires": {
+				"htmlparser2": "^3.10.0"
+			},
+			"dependencies": {
+				"entities": {
+					"version": "1.1.2",
+					"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+					"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
+					"dev": true
+				},
+				"htmlparser2": {
+					"version": "3.10.1",
+					"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+					"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+					"dev": true,
+					"requires": {
+						"domelementtype": "^1.3.1",
+						"domhandler": "^2.3.0",
+						"domutils": "^1.5.1",
+						"entities": "^1.1.1",
+						"inherits": "^2.0.1",
+						"readable-stream": "^3.1.1"
+					}
+				},
+				"readable-stream": {
+					"version": "3.6.0",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+					"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+					"dev": true,
+					"requires": {
+						"inherits": "^2.0.3",
+						"string_decoder": "^1.1.1",
+						"util-deprecate": "^1.0.1"
+					}
+				},
+				"safe-buffer": {
+					"version": "5.2.1",
+					"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+					"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+					"dev": true
+				},
+				"string_decoder": {
+					"version": "1.3.0",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+					"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+					"dev": true,
+					"requires": {
+						"safe-buffer": "~5.2.0"
+					}
+				}
+			}
+		},
+		"postcss-less": {
+			"version": "3.1.4",
+			"resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz",
+			"integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==",
+			"dev": true,
+			"requires": {
+				"postcss": "^7.0.14"
+			},
+			"dependencies": {
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+					"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^3.0.0"
+					}
+				}
+			}
+		},
+		"postcss-media-query-parser": {
+			"version": "0.2.3",
+			"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
+			"integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=",
+			"dev": true
+		},
+		"postcss-reporter": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz",
+			"integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==",
+			"dev": true,
+			"requires": {
+				"chalk": "^2.4.1",
+				"lodash": "^4.17.11",
+				"log-symbols": "^2.2.0",
+				"postcss": "^7.0.7"
+			},
+			"dependencies": {
+				"log-symbols": {
+					"version": "2.2.0",
+					"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+					"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.0.1"
+					}
+				},
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+					"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^3.0.0"
+					}
+				}
+			}
+		},
+		"postcss-resolve-nested-selector": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
+			"integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=",
+			"dev": true
+		},
+		"postcss-safe-parser": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz",
+			"integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==",
+			"dev": true,
+			"requires": {
+				"postcss": "^7.0.26"
+			},
+			"dependencies": {
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+					"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^3.0.0"
+					}
+				}
+			}
+		},
+		"postcss-sass": {
+			"version": "0.4.4",
+			"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz",
+			"integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==",
+			"dev": true,
+			"requires": {
+				"gonzales-pe": "^4.3.0",
+				"postcss": "^7.0.21"
+			},
+			"dependencies": {
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+					"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^3.0.0"
+					}
+				}
+			}
+		},
+		"postcss-scss": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.6.tgz",
+			"integrity": "sha512-4EFYGHcEw+H3E06PT/pQQri06u/1VIIPjeJQaM8skB80vZuXMhp4cSNV5azmdNkontnOID/XYWEvEEELLFB1ww==",
+			"dev": true,
+			"requires": {
+				"postcss": "^6.0.23"
+			}
+		},
+		"postcss-selector-parser": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
+			"integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+			"dev": true,
+			"requires": {
+				"cssesc": "^3.0.0",
+				"indexes-of": "^1.0.1",
+				"uniq": "^1.0.1"
+			}
+		},
+		"postcss-syntax": {
+			"version": "0.36.2",
+			"resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz",
+			"integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==",
+			"dev": true
+		},
+		"postcss-value-parser": {
+			"version": "3.3.1",
+			"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+			"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+			"dev": true
+		},
+		"pretty-bytes": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz",
+			"integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=",
+			"dev": true,
+			"requires": {
+				"number-is-nan": "^1.0.0"
+			}
+		},
+		"process-nextick-args": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+			"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+			"dev": true
+		},
+		"pseudomap": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+			"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+			"dev": true
+		},
+		"psl": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
+			"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
+			"dev": true
+		},
+		"punycode": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+			"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+			"dev": true
+		},
+		"qs": {
+			"version": "6.9.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz",
+			"integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==",
+			"dev": true
+		},
+		"quick-lru": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+			"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+			"dev": true
+		},
+		"raw-body": {
+			"version": "1.1.7",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
+			"integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=",
+			"dev": true,
+			"requires": {
+				"bytes": "1",
+				"string_decoder": "0.10"
+			}
+		},
+		"read-pkg": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+			"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+			"dev": true,
+			"requires": {
+				"load-json-file": "^1.0.0",
+				"normalize-package-data": "^2.3.2",
+				"path-type": "^1.0.0"
+			}
+		},
+		"read-pkg-up": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+			"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+			"dev": true,
+			"requires": {
+				"find-up": "^1.0.0",
+				"read-pkg": "^1.0.0"
+			}
+		},
+		"readable-stream": {
+			"version": "1.1.14",
+			"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+			"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+			"dev": true,
+			"requires": {
+				"core-util-is": "~1.0.0",
+				"inherits": "~2.0.1",
+				"isarray": "0.0.1",
+				"string_decoder": "~0.10.x"
+			},
+			"dependencies": {
+				"isarray": {
+					"version": "0.0.1",
+					"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+					"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+					"dev": true
+				}
+			}
+		},
+		"redent": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+			"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+			"dev": true,
+			"requires": {
+				"indent-string": "^2.1.0",
+				"strip-indent": "^1.0.1"
+			}
+		},
+		"regex-not": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+			"integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+			"dev": true,
+			"requires": {
+				"extend-shallow": "^3.0.2",
+				"safe-regex": "^1.1.0"
+			}
+		},
+		"remark": {
+			"version": "12.0.0",
+			"resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz",
+			"integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==",
+			"dev": true,
+			"requires": {
+				"remark-parse": "^8.0.0",
+				"remark-stringify": "^8.0.0",
+				"unified": "^9.0.0"
+			}
+		},
+		"remark-parse": {
+			"version": "8.0.2",
+			"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz",
+			"integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==",
+			"dev": true,
+			"requires": {
+				"ccount": "^1.0.0",
+				"collapse-white-space": "^1.0.2",
+				"is-alphabetical": "^1.0.0",
+				"is-decimal": "^1.0.0",
+				"is-whitespace-character": "^1.0.0",
+				"is-word-character": "^1.0.0",
+				"markdown-escapes": "^1.0.0",
+				"parse-entities": "^2.0.0",
+				"repeat-string": "^1.5.4",
+				"state-toggle": "^1.0.0",
+				"trim": "0.0.1",
+				"trim-trailing-lines": "^1.0.0",
+				"unherit": "^1.0.4",
+				"unist-util-remove-position": "^2.0.0",
+				"vfile-location": "^3.0.0",
+				"xtend": "^4.0.1"
+			}
+		},
+		"remark-stringify": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.0.0.tgz",
+			"integrity": "sha512-cABVYVloFH+2ZI5bdqzoOmemcz/ZuhQSH6W6ZNYnLojAUUn3xtX7u+6BpnYp35qHoGr2NFBsERV14t4vCIeW8w==",
+			"dev": true,
+			"requires": {
+				"ccount": "^1.0.0",
+				"is-alphanumeric": "^1.0.0",
+				"is-decimal": "^1.0.0",
+				"is-whitespace-character": "^1.0.0",
+				"longest-streak": "^2.0.1",
+				"markdown-escapes": "^1.0.0",
+				"markdown-table": "^2.0.0",
+				"mdast-util-compact": "^2.0.0",
+				"parse-entities": "^2.0.0",
+				"repeat-string": "^1.5.4",
+				"state-toggle": "^1.0.0",
+				"stringify-entities": "^3.0.0",
+				"unherit": "^1.0.4",
+				"xtend": "^4.0.1"
+			}
+		},
+		"repeat-element": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+			"integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+			"dev": true
+		},
+		"repeat-string": {
+			"version": "1.6.1",
+			"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+			"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+			"dev": true
+		},
+		"repeating": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+			"integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+			"dev": true,
+			"requires": {
+				"is-finite": "^1.0.0"
+			}
+		},
+		"replace-ext": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
+			"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
+			"dev": true
+		},
+		"request": {
+			"version": "2.88.0",
+			"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+			"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+			"dev": true,
+			"requires": {
+				"aws-sign2": "~0.7.0",
+				"aws4": "^1.8.0",
+				"caseless": "~0.12.0",
+				"combined-stream": "~1.0.6",
+				"extend": "~3.0.2",
+				"forever-agent": "~0.6.1",
+				"form-data": "~2.3.2",
+				"har-validator": "~5.1.0",
+				"http-signature": "~1.2.0",
+				"is-typedarray": "~1.0.0",
+				"isstream": "~0.1.2",
+				"json-stringify-safe": "~5.0.1",
+				"mime-types": "~2.1.19",
+				"oauth-sign": "~0.9.0",
+				"performance-now": "^2.1.0",
+				"qs": "~6.5.2",
+				"safe-buffer": "^5.1.2",
+				"tough-cookie": "~2.4.3",
+				"tunnel-agent": "^0.6.0",
+				"uuid": "^3.3.2"
+			},
+			"dependencies": {
+				"qs": {
+					"version": "6.5.2",
+					"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+					"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+					"dev": true
+				}
+			}
+		},
+		"require-directory": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+			"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+			"dev": true
+		},
+		"require-main-filename": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+			"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+			"dev": true
+		},
+		"resolve": {
+			"version": "1.12.0",
+			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+			"integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+			"dev": true,
+			"requires": {
+				"path-parse": "^1.0.6"
+			}
+		},
+		"resolve-dir": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+			"integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+			"dev": true,
+			"requires": {
+				"expand-tilde": "^2.0.0",
+				"global-modules": "^1.0.0"
+			}
+		},
+		"resolve-from": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+			"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+			"dev": true
+		},
+		"resolve-url": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+			"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+			"dev": true
+		},
+		"restore-cursor": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+			"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+			"dev": true,
+			"requires": {
+				"onetime": "^2.0.0",
+				"signal-exit": "^3.0.2"
+			}
+		},
+		"ret": {
+			"version": "0.1.15",
+			"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+			"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+			"dev": true
+		},
+		"reusify": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+			"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+			"dev": true
+		},
+		"rimraf": {
+			"version": "2.6.3",
+			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+			"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+			"dev": true,
+			"requires": {
+				"glob": "^7.1.3"
+			},
+			"dependencies": {
+				"glob": {
+					"version": "7.1.4",
+					"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+					"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+					"dev": true,
+					"requires": {
+						"fs.realpath": "^1.0.0",
+						"inflight": "^1.0.4",
+						"inherits": "2",
+						"minimatch": "^3.0.4",
+						"once": "^1.3.0",
+						"path-is-absolute": "^1.0.0"
+					}
+				}
+			}
+		},
+		"rtlcss": {
+			"version": "2.6.1",
+			"resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.6.1.tgz",
+			"integrity": "sha512-WoKAYLxT123Gh/9dAPeMY0YWBx6D7IjORJhTbmmq8UJI4zaSxsA0uqPWsXEqGgZT2HEGtxWkhkIZ6g1IvOQ+eg==",
+			"dev": true,
+			"requires": {
+				"@choojs/findup": "^0.2.1",
+				"chalk": "^2.4.2",
+				"mkdirp": "^0.5.1",
+				"postcss": "^6.0.23",
+				"strip-json-comments": "^2.0.0"
+			},
+			"dependencies": {
+				"strip-json-comments": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+					"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+					"dev": true
+				}
+			}
+		},
+		"run-async": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+			"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+			"dev": true,
+			"requires": {
+				"is-promise": "^2.1.0"
+			}
+		},
+		"run-parallel": {
+			"version": "1.1.9",
+			"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
+			"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
+			"dev": true
+		},
+		"rxjs": {
+			"version": "5.5.12",
+			"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz",
+			"integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==",
+			"dev": true,
+			"requires": {
+				"symbol-observable": "1.0.1"
+			}
+		},
+		"safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+			"dev": true
+		},
+		"safe-json-parse": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz",
+			"integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=",
+			"dev": true
+		},
+		"safe-regex": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+			"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+			"dev": true,
+			"requires": {
+				"ret": "~0.1.10"
+			}
+		},
+		"safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+			"dev": true
+		},
+		"sass-graph": {
+			"version": "2.2.5",
+			"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
+			"integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==",
+			"dev": true,
+			"requires": {
+				"glob": "^7.0.0",
+				"lodash": "^4.0.0",
+				"scss-tokenizer": "^0.2.3",
+				"yargs": "^13.3.2"
+			}
+		},
+		"sax": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+			"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+			"dev": true
+		},
+		"scss-tokenizer": {
+			"version": "0.2.3",
+			"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+			"integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+			"dev": true,
+			"requires": {
+				"js-base64": "^2.1.8",
+				"source-map": "^0.4.2"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.4.4",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+					"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+					"dev": true,
+					"requires": {
+						"amdefine": ">=0.0.4"
+					}
+				}
+			}
+		},
+		"semver": {
+			"version": "5.7.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+			"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+			"dev": true
+		},
+		"set-blocking": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+			"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+			"dev": true
+		},
+		"set-value": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+			"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+			"dev": true,
+			"requires": {
+				"extend-shallow": "^2.0.1",
+				"is-extendable": "^0.1.1",
+				"is-plain-object": "^2.0.3",
+				"split-string": "^3.0.1"
+			},
+			"dependencies": {
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				}
+			}
+		},
+		"shelljs": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
+			"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
+			"dev": true
+		},
+		"signal-exit": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+			"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+			"dev": true
+		},
+		"slash": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+			"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+			"dev": true
+		},
+		"slice-ansi": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+			"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+			"dev": true,
+			"requires": {
+				"ansi-styles": "^3.2.0",
+				"astral-regex": "^1.0.0",
+				"is-fullwidth-code-point": "^2.0.0"
+			}
+		},
+		"snapdragon": {
+			"version": "0.8.2",
+			"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+			"integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+			"dev": true,
+			"requires": {
+				"base": "^0.11.1",
+				"debug": "^2.2.0",
+				"define-property": "^0.2.5",
+				"extend-shallow": "^2.0.1",
+				"map-cache": "^0.2.2",
+				"source-map": "^0.5.6",
+				"source-map-resolve": "^0.5.0",
+				"use": "^3.1.0"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "0.2.5",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+					"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^0.1.0"
+					}
+				},
+				"extend-shallow": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+					"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+					"dev": true,
+					"requires": {
+						"is-extendable": "^0.1.0"
+					}
+				},
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				}
+			}
+		},
+		"snapdragon-node": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+			"integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+			"dev": true,
+			"requires": {
+				"define-property": "^1.0.0",
+				"isobject": "^3.0.0",
+				"snapdragon-util": "^3.0.1"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+					"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^1.0.0"
+					}
+				},
+				"is-accessor-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+					"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-data-descriptor": {
+					"version": "1.0.0",
+					"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+					"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+					"dev": true,
+					"requires": {
+						"kind-of": "^6.0.0"
+					}
+				},
+				"is-descriptor": {
+					"version": "1.0.2",
+					"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+					"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+					"dev": true,
+					"requires": {
+						"is-accessor-descriptor": "^1.0.0",
+						"is-data-descriptor": "^1.0.0",
+						"kind-of": "^6.0.2"
+					}
+				}
+			}
+		},
+		"snapdragon-util": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+			"integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+			"dev": true,
+			"requires": {
+				"kind-of": "^3.2.0"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"source-map": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+			"dev": true
+		},
+		"source-map-resolve": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+			"integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+			"dev": true,
+			"requires": {
+				"atob": "^2.1.1",
+				"decode-uri-component": "^0.2.0",
+				"resolve-url": "^0.2.1",
+				"source-map-url": "^0.4.0",
+				"urix": "^0.1.0"
+			}
+		},
+		"source-map-url": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+			"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+			"dev": true
+		},
+		"spdx-correct": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+			"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+			"dev": true,
+			"requires": {
+				"spdx-expression-parse": "^3.0.0",
+				"spdx-license-ids": "^3.0.0"
+			}
+		},
+		"spdx-exceptions": {
+			"version": "2.2.0",
+			"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+			"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+			"dev": true
+		},
+		"spdx-expression-parse": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+			"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+			"dev": true,
+			"requires": {
+				"spdx-exceptions": "^2.1.0",
+				"spdx-license-ids": "^3.0.0"
+			}
+		},
+		"spdx-license-ids": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+			"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+			"dev": true
+		},
+		"specificity": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz",
+			"integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==",
+			"dev": true
+		},
+		"split-string": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+			"integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+			"dev": true,
+			"requires": {
+				"extend-shallow": "^3.0.0"
+			}
+		},
+		"sprintf-js": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+			"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+			"dev": true
+		},
+		"sshpk": {
+			"version": "1.16.1",
+			"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+			"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+			"dev": true,
+			"requires": {
+				"asn1": "~0.2.3",
+				"assert-plus": "^1.0.0",
+				"bcrypt-pbkdf": "^1.0.0",
+				"dashdash": "^1.12.0",
+				"ecc-jsbn": "~0.1.1",
+				"getpass": "^0.1.1",
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.0.2",
+				"tweetnacl": "~0.14.0"
+			}
+		},
+		"stack-trace": {
+			"version": "0.0.10",
+			"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+			"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
+			"dev": true
+		},
+		"state-toggle": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",
+			"integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==",
+			"dev": true
+		},
+		"static-extend": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+			"integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+			"dev": true,
+			"requires": {
+				"define-property": "^0.2.5",
+				"object-copy": "^0.1.0"
+			},
+			"dependencies": {
+				"define-property": {
+					"version": "0.2.5",
+					"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+					"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+					"dev": true,
+					"requires": {
+						"is-descriptor": "^0.1.0"
+					}
+				}
+			}
+		},
+		"stdout-stream": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
+			"integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
+			"dev": true,
+			"requires": {
+				"readable-stream": "^2.0.1"
+			},
+			"dependencies": {
+				"readable-stream": {
+					"version": "2.3.7",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+					"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+					"dev": true,
+					"requires": {
+						"core-util-is": "~1.0.0",
+						"inherits": "~2.0.3",
+						"isarray": "~1.0.0",
+						"process-nextick-args": "~2.0.0",
+						"safe-buffer": "~5.1.1",
+						"string_decoder": "~1.1.1",
+						"util-deprecate": "~1.0.1"
+					}
+				},
+				"string_decoder": {
+					"version": "1.1.1",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+					"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+					"dev": true,
+					"requires": {
+						"safe-buffer": "~5.1.0"
+					}
+				}
+			}
+		},
+		"string-template": {
+			"version": "0.2.1",
+			"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
+			"integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=",
+			"dev": true
+		},
+		"string-width": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+			"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+			"dev": true,
+			"requires": {
+				"is-fullwidth-code-point": "^2.0.0",
+				"strip-ansi": "^4.0.0"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+					"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+					"dev": true
+				},
+				"strip-ansi": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+					"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^3.0.0"
+					}
+				}
+			}
+		},
+		"string_decoder": {
+			"version": "0.10.31",
+			"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+			"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+			"dev": true
+		},
+		"stringify-entities": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz",
+			"integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==",
+			"dev": true,
+			"requires": {
+				"character-entities-html4": "^1.0.0",
+				"character-entities-legacy": "^1.0.0",
+				"is-alphanumerical": "^1.0.0",
+				"is-decimal": "^1.0.2",
+				"is-hexadecimal": "^1.0.0"
+			}
+		},
+		"strip-ansi": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+			"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+			"dev": true,
+			"requires": {
+				"ansi-regex": "^2.0.0"
+			}
+		},
+		"strip-bom": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+			"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+			"dev": true,
+			"requires": {
+				"is-utf8": "^0.2.0"
+			}
+		},
+		"strip-indent": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+			"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+			"dev": true,
+			"requires": {
+				"get-stdin": "^4.0.1"
+			}
+		},
+		"strip-json-comments": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+			"integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=",
+			"dev": true
+		},
+		"style-search": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
+			"integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=",
+			"dev": true
+		},
+		"stylelint": {
+			"version": "13.5.0",
+			"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.5.0.tgz",
+			"integrity": "sha512-+Jy7ieKAWKTf2tmcAE7jgScxH39Urb87i0bjK/enScFaGWWaFn4kAPwepGOSk2b7CLUDVt/O6kwA0x0p/V7moQ==",
+			"dev": true,
+			"requires": {
+				"@stylelint/postcss-css-in-js": "^0.37.1",
+				"@stylelint/postcss-markdown": "^0.36.1",
+				"autoprefixer": "^9.7.6",
+				"balanced-match": "^1.0.0",
+				"chalk": "^4.0.0",
+				"cosmiconfig": "^6.0.0",
+				"debug": "^4.1.1",
+				"execall": "^2.0.0",
+				"file-entry-cache": "^5.0.1",
+				"get-stdin": "^8.0.0",
+				"global-modules": "^2.0.0",
+				"globby": "^11.0.0",
+				"globjoin": "^0.1.4",
+				"html-tags": "^3.1.0",
+				"ignore": "^5.1.4",
+				"import-lazy": "^4.0.0",
+				"imurmurhash": "^0.1.4",
+				"known-css-properties": "^0.19.0",
+				"leven": "^3.1.0",
+				"lodash": "^4.17.15",
+				"log-symbols": "^4.0.0",
+				"mathml-tag-names": "^2.1.3",
+				"meow": "^7.0.1",
+				"micromatch": "^4.0.2",
+				"normalize-selector": "^0.2.0",
+				"postcss": "^7.0.30",
+				"postcss-html": "^0.36.0",
+				"postcss-less": "^3.1.4",
+				"postcss-media-query-parser": "^0.2.3",
+				"postcss-reporter": "^6.0.1",
+				"postcss-resolve-nested-selector": "^0.1.1",
+				"postcss-safe-parser": "^4.0.2",
+				"postcss-sass": "^0.4.4",
+				"postcss-scss": "^2.0.0",
+				"postcss-selector-parser": "^6.0.2",
+				"postcss-syntax": "^0.36.2",
+				"postcss-value-parser": "^4.1.0",
+				"resolve-from": "^5.0.0",
+				"slash": "^3.0.0",
+				"specificity": "^0.4.1",
+				"string-width": "^4.2.0",
+				"strip-ansi": "^6.0.0",
+				"style-search": "^0.1.0",
+				"sugarss": "^2.0.0",
+				"svg-tags": "^1.0.0",
+				"table": "^5.4.6",
+				"v8-compile-cache": "^2.1.0",
+				"write-file-atomic": "^3.0.3"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+					"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+					"dev": true
+				},
+				"autoprefixer": {
+					"version": "9.8.0",
+					"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.0.tgz",
+					"integrity": "sha512-D96ZiIHXbDmU02dBaemyAg53ez+6F5yZmapmgKcjm35yEe1uVDYI8hGW3VYoGRaG290ZFf91YxHrR518vC0u/A==",
+					"dev": true,
+					"requires": {
+						"browserslist": "^4.12.0",
+						"caniuse-lite": "^1.0.30001061",
+						"chalk": "^2.4.2",
+						"normalize-range": "^0.1.2",
+						"num2fraction": "^1.2.2",
+						"postcss": "^7.0.30",
+						"postcss-value-parser": "^4.1.0"
+					},
+					"dependencies": {
+						"chalk": {
+							"version": "2.4.2",
+							"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+							"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+							"dev": true,
+							"requires": {
+								"ansi-styles": "^3.2.1",
+								"escape-string-regexp": "^1.0.5",
+								"supports-color": "^5.3.0"
+							}
+						}
+					}
+				},
+				"braces": {
+					"version": "3.0.2",
+					"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+					"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+					"dev": true,
+					"requires": {
+						"fill-range": "^7.0.1"
+					}
+				},
+				"browserslist": {
+					"version": "4.12.0",
+					"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
+					"integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+					"dev": true,
+					"requires": {
+						"caniuse-lite": "^1.0.30001043",
+						"electron-to-chromium": "^1.3.413",
+						"node-releases": "^1.1.53",
+						"pkg-up": "^2.0.0"
+					}
+				},
+				"camelcase": {
+					"version": "6.0.0",
+					"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
+					"integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==",
+					"dev": true
+				},
+				"camelcase-keys": {
+					"version": "6.2.2",
+					"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+					"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+					"dev": true,
+					"requires": {
+						"camelcase": "^5.3.1",
+						"map-obj": "^4.0.0",
+						"quick-lru": "^4.0.1"
+					},
+					"dependencies": {
+						"camelcase": {
+							"version": "5.3.1",
+							"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+							"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+							"dev": true
+						}
+					}
+				},
+				"caniuse-lite": {
+					"version": "1.0.30001066",
+					"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz",
+					"integrity": "sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==",
+					"dev": true
+				},
+				"chalk": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+					"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+					"dev": true,
+					"requires": {
+						"ansi-styles": "^4.1.0",
+						"supports-color": "^7.1.0"
+					},
+					"dependencies": {
+						"ansi-styles": {
+							"version": "4.2.1",
+							"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+							"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+							"dev": true,
+							"requires": {
+								"@types/color-name": "^1.1.1",
+								"color-convert": "^2.0.1"
+							}
+						},
+						"has-flag": {
+							"version": "4.0.0",
+							"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+							"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+							"dev": true
+						},
+						"supports-color": {
+							"version": "7.1.0",
+							"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+							"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+							"dev": true,
+							"requires": {
+								"has-flag": "^4.0.0"
+							}
+						}
+					}
+				},
+				"color-convert": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+					"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+					"dev": true,
+					"requires": {
+						"color-name": "~1.1.4"
+					}
+				},
+				"color-name": {
+					"version": "1.1.4",
+					"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+					"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+					"dev": true
+				},
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"electron-to-chromium": {
+					"version": "1.3.453",
+					"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.453.tgz",
+					"integrity": "sha512-IQbCfjJR0NDDn/+vojTlq7fPSREcALtF8M1n01gw7nQghCtfFYrJ2dfhsp8APr8bANoFC8vRTFVXMOGpT0eetw==",
+					"dev": true
+				},
+				"fill-range": {
+					"version": "7.0.1",
+					"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+					"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+					"dev": true,
+					"requires": {
+						"to-regex-range": "^5.0.1"
+					}
+				},
+				"find-up": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+					"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+					"dev": true,
+					"requires": {
+						"locate-path": "^5.0.0",
+						"path-exists": "^4.0.0"
+					}
+				},
+				"get-stdin": {
+					"version": "8.0.0",
+					"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+					"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
+					"dev": true
+				},
+				"global-modules": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+					"integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+					"dev": true,
+					"requires": {
+						"global-prefix": "^3.0.0"
+					}
+				},
+				"global-prefix": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+					"integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+					"dev": true,
+					"requires": {
+						"ini": "^1.3.5",
+						"kind-of": "^6.0.2",
+						"which": "^1.3.1"
+					}
+				},
+				"indent-string": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+					"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+					"dev": true
+				},
+				"is-fullwidth-code-point": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+					"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+					"dev": true
+				},
+				"is-number": {
+					"version": "7.0.0",
+					"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+					"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+					"dev": true
+				},
+				"locate-path": {
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+					"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+					"dev": true,
+					"requires": {
+						"p-locate": "^4.1.0"
+					}
+				},
+				"map-obj": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz",
+					"integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==",
+					"dev": true
+				},
+				"meow": {
+					"version": "7.0.1",
+					"resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz",
+					"integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==",
+					"dev": true,
+					"requires": {
+						"@types/minimist": "^1.2.0",
+						"arrify": "^2.0.1",
+						"camelcase": "^6.0.0",
+						"camelcase-keys": "^6.2.2",
+						"decamelize-keys": "^1.1.0",
+						"hard-rejection": "^2.1.0",
+						"minimist-options": "^4.0.2",
+						"normalize-package-data": "^2.5.0",
+						"read-pkg-up": "^7.0.1",
+						"redent": "^3.0.0",
+						"trim-newlines": "^3.0.0",
+						"type-fest": "^0.13.1",
+						"yargs-parser": "^18.1.3"
+					}
+				},
+				"micromatch": {
+					"version": "4.0.2",
+					"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+					"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+					"dev": true,
+					"requires": {
+						"braces": "^3.0.1",
+						"picomatch": "^2.0.5"
+					}
+				},
+				"ms": {
+					"version": "2.1.2",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+					"dev": true
+				},
+				"p-limit": {
+					"version": "2.3.0",
+					"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+					"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+					"dev": true,
+					"requires": {
+						"p-try": "^2.0.0"
+					}
+				},
+				"p-locate": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+					"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+					"dev": true,
+					"requires": {
+						"p-limit": "^2.2.0"
+					}
+				},
+				"p-try": {
+					"version": "2.2.0",
+					"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+					"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+					"dev": true
+				},
+				"parse-json": {
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
+					"integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+					"dev": true,
+					"requires": {
+						"@babel/code-frame": "^7.0.0",
+						"error-ex": "^1.3.1",
+						"json-parse-better-errors": "^1.0.1",
+						"lines-and-columns": "^1.1.6"
+					}
+				},
+				"path-exists": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+					"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+					"dev": true
+				},
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					},
+					"dependencies": {
+						"chalk": {
+							"version": "2.4.2",
+							"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+							"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+							"dev": true,
+							"requires": {
+								"ansi-styles": "^3.2.1",
+								"escape-string-regexp": "^1.0.5",
+								"supports-color": "^5.3.0"
+							},
+							"dependencies": {
+								"supports-color": {
+									"version": "5.5.0",
+									"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+									"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+									"dev": true,
+									"requires": {
+										"has-flag": "^3.0.0"
+									}
+								}
+							}
+						},
+						"supports-color": {
+							"version": "6.1.0",
+							"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+							"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+							"dev": true,
+							"requires": {
+								"has-flag": "^3.0.0"
+							}
+						}
+					}
+				},
+				"postcss-scss": {
+					"version": "2.1.1",
+					"resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz",
+					"integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==",
+					"dev": true,
+					"requires": {
+						"postcss": "^7.0.6"
+					}
+				},
+				"postcss-value-parser": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
+					"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
+					"dev": true
+				},
+				"read-pkg": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+					"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+					"dev": true,
+					"requires": {
+						"@types/normalize-package-data": "^2.4.0",
+						"normalize-package-data": "^2.5.0",
+						"parse-json": "^5.0.0",
+						"type-fest": "^0.6.0"
+					},
+					"dependencies": {
+						"type-fest": {
+							"version": "0.6.0",
+							"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+							"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+							"dev": true
+						}
+					}
+				},
+				"read-pkg-up": {
+					"version": "7.0.1",
+					"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+					"integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+					"dev": true,
+					"requires": {
+						"find-up": "^4.1.0",
+						"read-pkg": "^5.2.0",
+						"type-fest": "^0.8.1"
+					},
+					"dependencies": {
+						"type-fest": {
+							"version": "0.8.1",
+							"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+							"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+							"dev": true
+						}
+					}
+				},
+				"redent": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+					"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+					"dev": true,
+					"requires": {
+						"indent-string": "^4.0.0",
+						"strip-indent": "^3.0.0"
+					}
+				},
+				"string-width": {
+					"version": "4.2.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+					"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^8.0.0",
+						"is-fullwidth-code-point": "^3.0.0",
+						"strip-ansi": "^6.0.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "6.0.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+					"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^5.0.0"
+					}
+				},
+				"strip-indent": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+					"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+					"dev": true,
+					"requires": {
+						"min-indent": "^1.0.0"
+					}
+				},
+				"to-regex-range": {
+					"version": "5.0.1",
+					"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+					"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+					"dev": true,
+					"requires": {
+						"is-number": "^7.0.0"
+					}
+				},
+				"trim-newlines": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz",
+					"integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==",
+					"dev": true
+				},
+				"yargs-parser": {
+					"version": "18.1.3",
+					"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+					"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+					"dev": true,
+					"requires": {
+						"camelcase": "^5.0.0",
+						"decamelize": "^1.2.0"
+					},
+					"dependencies": {
+						"camelcase": {
+							"version": "5.3.1",
+							"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+							"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+							"dev": true
+						}
+					}
+				}
+			}
+		},
+		"stylelint-config-recommended": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz",
+			"integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==",
+			"dev": true
+		},
+		"stylelint-config-recommended-scss": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.0.0.tgz",
+			"integrity": "sha512-aEy0ENUrH4ASgFCu2mMcqBUAX0l4CPXg0XucJXdW+I7mdqJ7ICddkxP1eamBNBZ1QToc/wsuLmTQcalk3qYpsw==",
+			"dev": true,
+			"requires": {
+				"stylelint-config-recommended": "^3.0.0"
+			}
+		},
+		"stylelint-config-wordpress": {
+			"version": "15.0.0",
+			"resolved": "https://registry.npmjs.org/stylelint-config-wordpress/-/stylelint-config-wordpress-15.0.0.tgz",
+			"integrity": "sha512-53wJuCaS35MiO942ML3//lJ3sIvq9LC2Aw8y7DB9yIuO0Eqx2duWUFyxVQQDmEqDvcEg9gVXHKVca/YI+vMKIg==",
+			"dev": true,
+			"requires": {
+				"stylelint-config-recommended": "^3.0.0",
+				"stylelint-config-recommended-scss": "^4.0.0",
+				"stylelint-scss": "^3.11.1"
+			}
+		},
+		"stylelint-scss": {
+			"version": "3.11.1",
+			"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.11.1.tgz",
+			"integrity": "sha512-0FZNSfy5X2Or4VRA3Abwfrw1NHrI6jHT8ji9xSwP8Re2Kno0i90qbHwm8ohPO0kRB1RP9x1vCYBh4Tij+SZjIg==",
+			"dev": true,
+			"requires": {
+				"lodash": "^4.17.15",
+				"postcss-media-query-parser": "^0.2.3",
+				"postcss-resolve-nested-selector": "^0.1.1",
+				"postcss-selector-parser": "^6.0.2",
+				"postcss-value-parser": "^4.0.2"
+			},
+			"dependencies": {
+				"postcss-selector-parser": {
+					"version": "6.0.2",
+					"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
+					"integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+					"dev": true,
+					"requires": {
+						"cssesc": "^3.0.0",
+						"indexes-of": "^1.0.1",
+						"uniq": "^1.0.1"
+					}
+				},
+				"postcss-value-parser": {
+					"version": "4.0.2",
+					"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
+					"integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
+					"dev": true
+				}
+			}
+		},
+		"sugarss": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz",
+			"integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==",
+			"dev": true,
+			"requires": {
+				"postcss": "^7.0.2"
+			},
+			"dependencies": {
+				"postcss": {
+					"version": "7.0.31",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.31.tgz",
+					"integrity": "sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==",
+					"dev": true,
+					"requires": {
+						"chalk": "^2.4.2",
+						"source-map": "^0.6.1",
+						"supports-color": "^6.1.0"
+					}
+				},
+				"supports-color": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+					"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+					"dev": true,
+					"requires": {
+						"has-flag": "^3.0.0"
+					}
+				}
+			}
+		},
+		"supports-color": {
+			"version": "5.5.0",
+			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+			"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+			"dev": true,
+			"requires": {
+				"has-flag": "^3.0.0"
+			}
+		},
+		"svg-tags": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
+			"integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=",
+			"dev": true
+		},
+		"symbol-observable": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
+			"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
+			"dev": true
+		},
+		"table": {
+			"version": "5.4.6",
+			"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+			"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+			"dev": true,
+			"requires": {
+				"ajv": "^6.10.2",
+				"lodash": "^4.17.14",
+				"slice-ansi": "^2.1.0",
+				"string-width": "^3.0.0"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+					"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+					"dev": true
+				},
+				"emoji-regex": {
+					"version": "7.0.3",
+					"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+					"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+					"dev": true
+				},
+				"string-width": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+					"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^7.0.1",
+						"is-fullwidth-code-point": "^2.0.0",
+						"strip-ansi": "^5.1.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+					"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^4.1.0"
+					}
+				}
+			}
+		},
+		"tar": {
+			"version": "2.2.2",
+			"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
+			"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
+			"dev": true,
+			"requires": {
+				"block-stream": "*",
+				"fstream": "^1.0.12",
+				"inherits": "2"
+			}
+		},
+		"text-table": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+			"dev": true
+		},
+		"through": {
+			"version": "2.3.8",
+			"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+			"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+			"dev": true
+		},
+		"tiny-lr": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",
+			"integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==",
+			"dev": true,
+			"requires": {
+				"body": "^5.1.0",
+				"debug": "^3.1.0",
+				"faye-websocket": "~0.10.0",
+				"livereload-js": "^2.3.0",
+				"object-assign": "^4.1.0",
+				"qs": "^6.4.0"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "3.2.6",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+					"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"ms": {
+					"version": "2.1.2",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+					"dev": true
+				}
+			}
+		},
+		"tmp": {
+			"version": "0.0.33",
+			"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+			"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+			"dev": true,
+			"requires": {
+				"os-tmpdir": "~1.0.2"
+			}
+		},
+		"to-fast-properties": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+			"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+			"dev": true
+		},
+		"to-object-path": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+			"integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+			"dev": true,
+			"requires": {
+				"kind-of": "^3.0.2"
+			},
+			"dependencies": {
+				"kind-of": {
+					"version": "3.2.2",
+					"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+					"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+					"dev": true,
+					"requires": {
+						"is-buffer": "^1.1.5"
+					}
+				}
+			}
+		},
+		"to-regex": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+			"integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+			"dev": true,
+			"requires": {
+				"define-property": "^2.0.2",
+				"extend-shallow": "^3.0.2",
+				"regex-not": "^1.0.2",
+				"safe-regex": "^1.1.0"
+			}
+		},
+		"to-regex-range": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+			"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+			"dev": true,
+			"requires": {
+				"is-number": "^3.0.0",
+				"repeat-string": "^1.6.1"
+			}
+		},
+		"tough-cookie": {
+			"version": "2.4.3",
+			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+			"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+			"dev": true,
+			"requires": {
+				"psl": "^1.1.24",
+				"punycode": "^1.4.1"
+			},
+			"dependencies": {
+				"punycode": {
+					"version": "1.4.1",
+					"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+					"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+					"dev": true
+				}
+			}
+		},
+		"trim": {
+			"version": "0.0.1",
+			"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
+			"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=",
+			"dev": true
+		},
+		"trim-newlines": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+			"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+			"dev": true
+		},
+		"trim-trailing-lines": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
+			"integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==",
+			"dev": true
+		},
+		"trough": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
+			"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
+			"dev": true
+		},
+		"true-case-path": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
+			"integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
+			"dev": true,
+			"requires": {
+				"glob": "^7.1.2"
+			},
+			"dependencies": {
+				"glob": {
+					"version": "7.1.6",
+					"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+					"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+					"dev": true,
+					"requires": {
+						"fs.realpath": "^1.0.0",
+						"inflight": "^1.0.4",
+						"inherits": "2",
+						"minimatch": "^3.0.4",
+						"once": "^1.3.0",
+						"path-is-absolute": "^1.0.0"
+					}
+				}
+			}
+		},
+		"tunnel-agent": {
+			"version": "0.6.0",
+			"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+			"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+			"dev": true,
+			"requires": {
+				"safe-buffer": "^5.0.1"
+			}
+		},
+		"tweetnacl": {
+			"version": "0.14.5",
+			"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+			"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+			"dev": true
+		},
+		"type-fest": {
+			"version": "0.13.1",
+			"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+			"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+			"dev": true
+		},
+		"typedarray": {
+			"version": "0.0.6",
+			"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+			"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+			"dev": true
+		},
+		"typedarray-to-buffer": {
+			"version": "3.1.5",
+			"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+			"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+			"dev": true,
+			"requires": {
+				"is-typedarray": "^1.0.0"
+			}
+		},
+		"uglify-js": {
+			"version": "3.0.28",
+			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.28.tgz",
+			"integrity": "sha512-0h/qGay016GG2lVav3Kz174F3T2Vjlz2v6HCt+WDQpoXfco0hWwF5gHK9yh88mUYvIC+N7Z8NT8WpjSp1yoqGA==",
+			"dev": true,
+			"requires": {
+				"commander": "~2.11.0",
+				"source-map": "~0.5.1"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.5.7",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+					"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+					"dev": true
+				}
+			}
+		},
+		"underscore": {
+			"version": "1.8.3",
+			"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
+			"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
+			"dev": true
+		},
+		"underscore.string": {
+			"version": "3.3.5",
+			"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz",
+			"integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==",
+			"dev": true,
+			"requires": {
+				"sprintf-js": "^1.0.3",
+				"util-deprecate": "^1.0.2"
+			}
+		},
+		"unherit": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
+			"integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==",
+			"dev": true,
+			"requires": {
+				"inherits": "^2.0.0",
+				"xtend": "^4.0.0"
+			}
+		},
+		"unified": {
+			"version": "9.0.0",
+			"resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz",
+			"integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==",
+			"dev": true,
+			"requires": {
+				"bail": "^1.0.0",
+				"extend": "^3.0.0",
+				"is-buffer": "^2.0.0",
+				"is-plain-obj": "^2.0.0",
+				"trough": "^1.0.0",
+				"vfile": "^4.0.0"
+			},
+			"dependencies": {
+				"is-buffer": {
+					"version": "2.0.4",
+					"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+					"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+					"dev": true
+				}
+			}
+		},
+		"union-value": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+			"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+			"dev": true,
+			"requires": {
+				"arr-union": "^3.1.0",
+				"get-value": "^2.0.6",
+				"is-extendable": "^0.1.1",
+				"set-value": "^2.0.1"
+			}
+		},
+		"uniq": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+			"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+			"dev": true
+		},
+		"unist-util-find-all-after": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz",
+			"integrity": "sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw==",
+			"dev": true,
+			"requires": {
+				"unist-util-is": "^4.0.0"
+			}
+		},
+		"unist-util-is": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz",
+			"integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==",
+			"dev": true
+		},
+		"unist-util-remove-position": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz",
+			"integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==",
+			"dev": true,
+			"requires": {
+				"unist-util-visit": "^2.0.0"
+			}
+		},
+		"unist-util-stringify-position": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+			"integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+			"dev": true,
+			"requires": {
+				"@types/unist": "^2.0.2"
+			}
+		},
+		"unist-util-visit": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz",
+			"integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==",
+			"dev": true,
+			"requires": {
+				"@types/unist": "^2.0.0",
+				"unist-util-is": "^4.0.0",
+				"unist-util-visit-parents": "^3.0.0"
+			}
+		},
+		"unist-util-visit-parents": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz",
+			"integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==",
+			"dev": true,
+			"requires": {
+				"@types/unist": "^2.0.0",
+				"unist-util-is": "^4.0.0"
+			}
+		},
+		"unset-value": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+			"integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+			"dev": true,
+			"requires": {
+				"has-value": "^0.3.1",
+				"isobject": "^3.0.0"
+			},
+			"dependencies": {
+				"has-value": {
+					"version": "0.3.1",
+					"resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+					"integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+					"dev": true,
+					"requires": {
+						"get-value": "^2.0.3",
+						"has-values": "^0.1.4",
+						"isobject": "^2.0.0"
+					},
+					"dependencies": {
+						"isobject": {
+							"version": "2.1.0",
+							"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+							"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+							"dev": true,
+							"requires": {
+								"isarray": "1.0.0"
+							}
+						}
+					}
+				},
+				"has-values": {
+					"version": "0.1.4",
+					"resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+					"integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+					"dev": true
+				}
+			}
+		},
+		"untildify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz",
+			"integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=",
+			"dev": true,
+			"requires": {
+				"os-homedir": "^1.0.0"
+			}
+		},
+		"uri-js": {
+			"version": "4.2.2",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+			"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+			"dev": true,
+			"requires": {
+				"punycode": "^2.1.0"
+			}
+		},
+		"uri-path": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz",
+			"integrity": "sha1-l0fwGDWJM8Md4PzP2C0TjmcmLjI=",
+			"dev": true
+		},
+		"urix": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+			"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+			"dev": true
+		},
+		"use": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+			"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+			"dev": true
+		},
+		"util-deprecate": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+			"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+			"dev": true
+		},
+		"uuid": {
+			"version": "3.3.3",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+			"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
+			"dev": true
+		},
+		"v8-compile-cache": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+			"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+			"dev": true
+		},
+		"validate-npm-package-license": {
+			"version": "3.0.4",
+			"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+			"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+			"dev": true,
+			"requires": {
+				"spdx-correct": "^3.0.0",
+				"spdx-expression-parse": "^3.0.0"
+			}
+		},
+		"verror": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+			"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+			"dev": true,
+			"requires": {
+				"assert-plus": "^1.0.0",
+				"core-util-is": "1.0.2",
+				"extsprintf": "^1.2.0"
+			}
+		},
+		"vfile": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz",
+			"integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==",
+			"dev": true,
+			"requires": {
+				"@types/unist": "^2.0.0",
+				"is-buffer": "^2.0.0",
+				"replace-ext": "1.0.0",
+				"unist-util-stringify-position": "^2.0.0",
+				"vfile-message": "^2.0.0"
+			},
+			"dependencies": {
+				"is-buffer": {
+					"version": "2.0.4",
+					"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+					"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+					"dev": true
+				}
+			}
+		},
+		"vfile-location": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz",
+			"integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==",
+			"dev": true
+		},
+		"vfile-message": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+			"integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+			"dev": true,
+			"requires": {
+				"@types/unist": "^2.0.0",
+				"unist-util-stringify-position": "^2.0.0"
+			}
+		},
+		"websocket-driver": {
+			"version": "0.7.3",
+			"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+			"integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+			"dev": true,
+			"requires": {
+				"http-parser-js": ">=0.4.0 <0.4.11",
+				"safe-buffer": ">=5.1.0",
+				"websocket-extensions": ">=0.1.1"
+			}
+		},
+		"websocket-extensions": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+			"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+			"dev": true
+		},
+		"which": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+			"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+			"dev": true,
+			"requires": {
+				"isexe": "^2.0.0"
+			}
+		},
+		"which-module": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+			"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+			"dev": true
+		},
+		"wide-align": {
+			"version": "1.1.3",
+			"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+			"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+			"dev": true,
+			"requires": {
+				"string-width": "^1.0.2 || 2"
+			}
+		},
+		"wordwrap": {
+			"version": "0.0.3",
+			"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+			"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+			"dev": true
+		},
+		"wrap-ansi": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+			"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+			"dev": true,
+			"requires": {
+				"ansi-styles": "^3.2.0",
+				"string-width": "^3.0.0",
+				"strip-ansi": "^5.0.0"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+					"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+					"dev": true
+				},
+				"emoji-regex": {
+					"version": "7.0.3",
+					"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+					"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+					"dev": true
+				},
+				"string-width": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+					"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^7.0.1",
+						"is-fullwidth-code-point": "^2.0.0",
+						"strip-ansi": "^5.1.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+					"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^4.1.0"
+					}
+				}
+			}
+		},
+		"wrappy": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+			"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+			"dev": true
+		},
+		"write": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+			"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+			"dev": true,
+			"requires": {
+				"mkdirp": "^0.5.1"
+			}
+		},
+		"write-file-atomic": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
+			"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
+			"dev": true,
+			"requires": {
+				"imurmurhash": "^0.1.4",
+				"is-typedarray": "^1.0.0",
+				"signal-exit": "^3.0.2",
+				"typedarray-to-buffer": "^3.1.5"
+			}
+		},
+		"xmlbuilder": {
+			"version": "8.2.2",
+			"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
+			"integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=",
+			"dev": true
+		},
+		"xmlrpc": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz",
+			"integrity": "sha1-JrLqNHhI0Ciqx+dRS1NRl23j6D0=",
+			"dev": true,
+			"requires": {
+				"sax": "1.2.x",
+				"xmlbuilder": "8.2.x"
+			}
+		},
+		"xtend": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+			"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+			"dev": true
+		},
+		"y18n": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+			"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+			"dev": true
+		},
+		"yallist": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+			"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+			"dev": true
+		},
+		"yaml": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+			"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
+			"dev": true
+		},
+		"yargs": {
+			"version": "13.3.2",
+			"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+			"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+			"dev": true,
+			"requires": {
+				"cliui": "^5.0.0",
+				"find-up": "^3.0.0",
+				"get-caller-file": "^2.0.1",
+				"require-directory": "^2.1.1",
+				"require-main-filename": "^2.0.0",
+				"set-blocking": "^2.0.0",
+				"string-width": "^3.0.0",
+				"which-module": "^2.0.0",
+				"y18n": "^4.0.0",
+				"yargs-parser": "^13.1.2"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+					"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+					"dev": true
+				},
+				"emoji-regex": {
+					"version": "7.0.3",
+					"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+					"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+					"dev": true
+				},
+				"find-up": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+					"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+					"dev": true,
+					"requires": {
+						"locate-path": "^3.0.0"
+					}
+				},
+				"locate-path": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+					"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+					"dev": true,
+					"requires": {
+						"p-locate": "^3.0.0",
+						"path-exists": "^3.0.0"
+					}
+				},
+				"p-limit": {
+					"version": "2.3.0",
+					"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+					"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+					"dev": true,
+					"requires": {
+						"p-try": "^2.0.0"
+					}
+				},
+				"p-locate": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+					"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+					"dev": true,
+					"requires": {
+						"p-limit": "^2.0.0"
+					}
+				},
+				"p-try": {
+					"version": "2.2.0",
+					"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+					"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+					"dev": true
+				},
+				"path-exists": {
+					"version": "3.0.0",
+					"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+					"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+					"dev": true
+				},
+				"string-width": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+					"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^7.0.1",
+						"is-fullwidth-code-point": "^2.0.0",
+						"strip-ansi": "^5.1.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+					"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^4.1.0"
+					}
+				}
+			}
+		},
+		"yargs-parser": {
+			"version": "13.1.2",
+			"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+			"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+			"dev": true,
+			"requires": {
+				"camelcase": "^5.0.0",
+				"decamelize": "^1.2.0"
+			},
+			"dependencies": {
+				"camelcase": {
+					"version": "5.3.1",
+					"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+					"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+					"dev": true
+				}
+			}
+		}
+	}
+}
Index: /tags/2.6.6/package.json
===================================================================
--- /tags/2.6.6/package.json	(revision 7162)
+++ /tags/2.6.6/package.json	(revision 7162)
@@ -0,0 +1,56 @@
+{
+	"author": "The bbPress Contributors",
+	"bugs": {
+		"url": "https://bbpress.trac.wordpress.org/"
+	},
+	"description": "Forum software with a twist from the creators of WordPress.",
+	"devDependencies": {
+		"@wordpress/browserslist-config": "~2.1.3",
+		"autoprefixer": "~8.2.0",
+		"grunt": "~1.1.0",
+		"grunt-check-dependencies": "~1.0.0",
+		"grunt-checktextdomain": "~1.0.1",
+		"grunt-contrib-clean": "~1.1.0",
+		"grunt-contrib-copy": "~1.0.0",
+		"grunt-contrib-cssmin": "~2.2.0",
+		"grunt-contrib-jshint": "~1.1.0",
+		"grunt-contrib-uglify": "~3.0.1",
+		"grunt-contrib-watch": "~1.1.0",
+		"grunt-exec": "~2.0.0",
+		"grunt-jsvalidate": "~0.2.2",
+		"grunt-legacy-util": "~1.1.1",
+		"grunt-patch-wordpress": "~1.0.0",
+		"grunt-postcss": "~0.8.0",
+		"grunt-rtlcss": "~2.0.2",
+		"grunt-sass": "~3.0.2",
+		"grunt-stylelint": "~0.8.0",
+		"grunt-wp-i18n": "~1.0.3",
+		"matchdep": "~2.0.0",
+		"node-sass": "~4.14.1",
+		"postcss-scss": "~1.0.2",
+		"stylelint": "~13.5.0",
+		"stylelint-config-wordpress": "~15.0.0"
+	},
+	"engines": {
+		"node": ">=6.9.1"
+	},
+	"keywords": [
+		"community",
+		"forums",
+		"topics",
+		"replies",
+		"profiles",
+		"support"
+	],
+	"homepage": "https://bbpress.org",
+	"license": "GPL-2.0-or-later",
+	"name": "bbpress",
+	"repository": {
+		"type": "svn",
+		"url": "https://bbpress.svn.wordpress.org/branches/2.6"
+	},
+	"version": "2.6.6",
+	"browserslist": [
+		"extends @wordpress/browserslist-config"
+	]
+}
Index: /tags/2.6.6/phpunit.xml.dist
===================================================================
--- /tags/2.6.6/phpunit.xml.dist	(revision 7162)
+++ /tags/2.6.6/phpunit.xml.dist	(revision 7162)
@@ -0,0 +1,35 @@
+<phpunit
+	bootstrap="tests/phpunit/bootstrap.php"
+	backupGlobals="false"
+	colors="true"
+	convertErrorsToExceptions="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	>
+	<testsuites>
+		<testsuite>
+			<directory suffix=".php">tests/phpunit/testcases/</directory>
+		</testsuite>
+	</testsuites>
+	<groups>
+		<exclude>
+			<group>buddypress</group>
+		</exclude>
+	</groups>
+	<listeners>
+		<listener class="SpeedTrapListener" file="tests/phpunit/includes/speed-trap-listener.php">
+			<arguments>
+				<array>
+					<element key="slowThreshold">
+						<integer>500</integer>
+					</element>
+				</array>
+			</arguments>
+		</listener>
+	</listeners>
+	<filter>
+		<whitelist processUncoveredFilesFromWhitelist="true">
+			<directory suffix=".php">src</directory>
+		</whitelist>
+	</filter>
+</phpunit>
Index: /tags/2.6.6/src/bbpress.php
===================================================================
--- /tags/2.6.6/src/bbpress.php	(revision 7162)
+++ /tags/2.6.6/src/bbpress.php	(revision 7162)
@@ -0,0 +1,1064 @@
+<?php
+
+/**
+ * The bbPress Plugin
+ *
+ * bbPress is forum software with a twist from the creators of WordPress.
+ *
+ * $Id$
+ *
+ * @package bbPress
+ * @subpackage Main
+ */
+
+/**
+ * Plugin Name:       bbPress
+ * Plugin URI:        https://bbpress.org
+ * Description:       bbPress is forum software with a twist from the creators of WordPress.
+ * Author:            The bbPress Contributors
+ * Author URI:        https://bbpress.org
+ * Version:           2.6.6
+ * Text Domain:       bbpress
+ * Domain Path:       /languages/
+ * License:           GPLv2 or later (license.txt)
+ * Requires PHP:      5.6.20
+ * Requires at least: 5.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'bbPress' ) ) :
+/**
+ * Main bbPress Class
+ *
+ * "Word hard. Stay bumble."
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+final class bbPress {
+
+	/** Magic *****************************************************************/
+
+	/**
+	 * bbPress uses many variables, several of which can be filtered to
+	 * customize the way it operates. Most of these variables are stored in a
+	 * private array that gets updated with the help of PHP magic methods.
+	 *
+	 * This is a precautionary measure, to avoid potential errors produced by
+	 * unanticipated direct manipulation of run-time data.
+	 *
+	 * @see bbPress::setup_globals()
+	 * @var array
+	 */
+	private $data;
+
+	/** Not Magic *************************************************************/
+
+	/**
+	 * @var mixed False when not logged in; WP_User object when logged in
+	 */
+	public $current_user = false;
+
+	/**
+	 * @var stdClass Add-ons append to this (Akismet, BuddyPress, etc...)
+	 */
+	public $extend;
+
+	/**
+	 * @var array Topic views
+	 */
+	public $views = array();
+
+	/**
+	 * @var array Overloads get_option()
+	 */
+	public $options = array();
+
+	/**
+	 * @var array Storage of options not in the database
+	 */
+	public $not_options = array();
+
+	/**
+	 * @var array Overloads get_user_meta()
+	 */
+	public $user_options = array();
+
+	/**
+	 * @var array Dynamically initialized user roles
+	 */
+	public $roles = array();
+
+	/** Singleton *************************************************************/
+
+	/**
+	 * Main bbPress Instance
+	 *
+	 * bbPress is fun
+	 * Please load it only one time
+	 * For this, we thank you
+	 *
+	 * Insures that only one instance of bbPress exists in memory at any one
+	 * time. Also prevents needing to define globals all over the place.
+	 *
+	 * @since 2.1.0 bbPress (r3757)
+	 *
+	 * @staticvar object $instance
+	 * @see bbpress()
+	 * @return bbPress The one true bbPress
+	 */
+	public static function instance() {
+
+		// Store the instance locally to avoid private static replication
+		static $instance = null;
+
+		// Only run these methods if they haven't been ran previously
+		if ( null === $instance ) {
+			$instance = new bbPress();
+			$instance->setup_environment();
+			$instance->includes();
+			$instance->setup_variables();
+			$instance->setup_actions();
+		}
+
+		// Always return the instance
+		return $instance;
+	}
+
+	/** Magic Methods *********************************************************/
+
+	/**
+	 * A dummy constructor to prevent bbPress from being loaded more than once.
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @see bbPress::instance()
+	 * @see bbpress();
+	 */
+	private function __construct() { /* Do nothing here */ }
+
+	/**
+	 * A dummy magic method to prevent bbPress from being cloned
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 */
+	public function __clone() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'bbpress' ), '2.1' ); }
+
+	/**
+	 * A dummy magic method to prevent bbPress from being unserialized
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 */
+	public function __wakeup() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'bbpress' ), '2.1' ); }
+
+	/**
+	 * Magic method for checking the existence of a certain custom field
+	 *
+	 * @since 2.1.0 bbPress (r3951)
+	 */
+	public function __isset( $key ) { return isset( $this->data[ $key ] ); }
+
+	/**
+	 * Magic method for getting bbPress variables
+	 *
+	 * @since 2.1.0 bbPress (r3951)
+	 */
+	public function __get( $key ) { return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null; }
+
+	/**
+	 * Magic method for setting bbPress variables
+	 *
+	 * @since 2.1.0 bbPress (r3951)
+	 */
+	public function __set( $key , $value ) { $this->data[ $key ] = $value; }
+
+	/**
+	 * Magic method for unsetting bbPress variables
+	 *
+	 * @since 2.3.0 bbPress (r4628)
+	 */
+	public function __unset( $key ) {
+		if ( isset( $this->data[ $key ] ) ) {
+			unset( $this->data[ $key ] );
+		}
+	}
+
+	/**
+	 * Magic method to prevent notices and errors from invalid method calls
+	 *
+	 * @since 2.2.0 bbPress (r4252)
+	 */
+	public function __call( $name = '', $args = array() ) { unset( $name, $args ); return null; }
+
+	/** Private Methods *******************************************************/
+
+	/**
+	 * Setup the environment variables to allow the rest of bbPress to function
+	 * more easily.
+	 *
+	 * @since 2.0.0 bbPress (r2626)
+	 *
+	 * @access private
+	 */
+	private function setup_environment() {
+
+		/** Versions **********************************************************/
+
+		$this->version    = '2.6.6';
+		$this->db_version = '263';
+
+		/** Paths *************************************************************/
+
+		// File & base
+		$this->file         = __FILE__;
+		$this->basename     = apply_filters( 'bbp_plugin_basename', str_replace( array( 'build/', 'src/' ), '', plugin_basename( $this->file ) ) );
+		$this->basepath     = apply_filters( 'bbp_plugin_basepath', trailingslashit( dirname( $this->basename ) ) );
+
+		// Path and URL
+		$this->plugin_dir   = apply_filters( 'bbp_plugin_dir_path', plugin_dir_path( $this->file ) );
+		$this->plugin_url   = apply_filters( 'bbp_plugin_dir_url',  plugin_dir_url ( $this->file ) );
+
+		// Includes
+		$this->includes_dir = apply_filters( 'bbp_includes_dir', trailingslashit( $this->plugin_dir . 'includes'  ) );
+		$this->includes_url = apply_filters( 'bbp_includes_url', trailingslashit( $this->plugin_url . 'includes'  ) );
+
+		// Languages
+		$this->lang_base    = apply_filters( 'bbp_lang_base',    trailingslashit( $this->basepath   . 'languages' ) );
+		$this->lang_dir     = apply_filters( 'bbp_lang_dir',     trailingslashit( $this->plugin_dir . 'languages' ) );
+
+		// Templates
+		$this->themes_dir   = apply_filters( 'bbp_themes_dir',   trailingslashit( $this->plugin_dir . 'templates' ) );
+		$this->themes_url   = apply_filters( 'bbp_themes_url',   trailingslashit( $this->plugin_url . 'templates' ) );
+	}
+
+	/**
+	 * Smart defaults to many bbPress specific class variables.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 */
+	private function setup_variables() {
+
+		/** Identifiers *******************************************************/
+
+		// Post type identifiers
+		$this->forum_post_type   = apply_filters( 'bbp_forum_post_type',  'forum'     );
+		$this->topic_post_type   = apply_filters( 'bbp_topic_post_type',  'topic'     );
+		$this->topic_tag_tax_id  = apply_filters( 'bbp_topic_tag_tax_id', 'topic-tag' );
+		$this->reply_post_type   = apply_filters( 'bbp_reply_post_type',  'reply'     );
+
+		// Status identifiers
+		$this->spam_status_id    = apply_filters( 'bbp_spam_post_status',    'spam'    );
+		$this->closed_status_id  = apply_filters( 'bbp_closed_post_status',  'closed'  );
+		$this->orphan_status_id  = apply_filters( 'bbp_orphan_post_status',  'orphan'  );
+		$this->public_status_id  = apply_filters( 'bbp_public_post_status',  'publish' );
+		$this->pending_status_id = apply_filters( 'bbp_pending_post_status', 'pending' );
+		$this->private_status_id = apply_filters( 'bbp_private_post_status', 'private' );
+		$this->hidden_status_id  = apply_filters( 'bbp_hidden_post_status',  'hidden'  );
+		$this->trash_status_id   = apply_filters( 'bbp_trash_post_status',   'trash'   );
+
+		// Other identifiers
+		$this->user_id           = apply_filters( 'bbp_user_id',        'bbp_user'        );
+		$this->tops_id           = apply_filters( 'bbp_tops_id',        'bbp_tops'        );
+		$this->reps_id           = apply_filters( 'bbp_reps_id',        'bbp_reps'        );
+		$this->favs_id           = apply_filters( 'bbp_favs_id',        'bbp_favs'        );
+		$this->subs_id           = apply_filters( 'bbp_subs_id',        'bbp_subs'        );
+		$this->view_id           = apply_filters( 'bbp_view_id',        'bbp_view'        );
+		$this->edit_id           = apply_filters( 'bbp_edit_id',        'edit'            );
+		$this->paged_id          = apply_filters( 'bbp_paged_id',       'paged'           );
+		$this->search_id         = apply_filters( 'bbp_search_id',      'bbp_search'      );
+		$this->engagements_id    = apply_filters( 'bbp_engagements_id', 'bbp_engagements' );
+
+		/** Queries ***********************************************************/
+
+		$this->current_view_id      = 0; // Current view id
+		$this->current_forum_id     = 0; // Current forum id
+		$this->current_topic_id     = 0; // Current topic id
+		$this->current_reply_id     = 0; // Current reply id
+		$this->current_topic_tag_id = 0; // Current topic tag id
+		$this->current_user_id      = 0; // Current topic tag id
+
+		$this->forum_query    = new WP_Query();       // Main forum query
+		$this->topic_query    = new WP_Query();       // Main topic query
+		$this->reply_query    = new WP_Query();       // Main reply query
+		$this->search_query   = new WP_Query();       // Main search query
+		$this->user_query     = new BBP_User_Query(); // Main user query
+
+		/** Theme Compat ******************************************************/
+
+		$this->theme_compat   = new stdClass(); // Base theme compatibility class
+		$this->filters        = new stdClass(); // Used when adding/removing filters
+
+		/** Users *************************************************************/
+
+		$this->current_user   = new WP_User(); // Currently logged in user
+		$this->displayed_user = new WP_User(); // Currently displayed user
+
+		/** Misc **************************************************************/
+
+		$this->domain         = 'bbpress';      // Unique identifier for retrieving translated strings
+		$this->extend         = new stdClass(); // Plugins add data here
+		$this->errors         = new WP_Error(); // Feedback
+
+		/** Deprecated ********************************************************/
+
+		$this->tab_index      = apply_filters( 'bbp_default_tab_index', 100 );
+	}
+
+	/**
+	 * Include required files
+	 *
+	 * @since 2.0.0 bbPress (r2626)
+	 *
+	 * @access private
+	 */
+	private function includes() {
+
+		/** Core **************************************************************/
+
+		require $this->includes_dir . 'core/abstraction.php';
+		require $this->includes_dir . 'core/sub-actions.php';
+		require $this->includes_dir . 'core/functions.php';
+		require $this->includes_dir . 'core/cache.php';
+		require $this->includes_dir . 'core/options.php';
+		require $this->includes_dir . 'core/capabilities.php';
+		require $this->includes_dir . 'core/update.php';
+		require $this->includes_dir . 'core/template-functions.php';
+		require $this->includes_dir . 'core/template-loader.php';
+		require $this->includes_dir . 'core/theme-compat.php';
+
+		/** Components ********************************************************/
+
+		// Common
+		require $this->includes_dir . 'common/ajax.php';
+		require $this->includes_dir . 'common/classes.php';
+		require $this->includes_dir . 'common/engagements.php';
+		require $this->includes_dir . 'common/functions.php';
+		require $this->includes_dir . 'common/formatting.php';
+		require $this->includes_dir . 'common/locale.php';
+		require $this->includes_dir . 'common/locks.php';
+		require $this->includes_dir . 'common/template.php';
+		require $this->includes_dir . 'common/widgets.php';
+		require $this->includes_dir . 'common/shortcodes.php';
+
+		// Forums
+		require $this->includes_dir . 'forums/capabilities.php';
+		require $this->includes_dir . 'forums/functions.php';
+		require $this->includes_dir . 'forums/template.php';
+
+		// Topics
+		require $this->includes_dir . 'topics/capabilities.php';
+		require $this->includes_dir . 'topics/functions.php';
+		require $this->includes_dir . 'topics/template.php';
+
+		// Replies
+		require $this->includes_dir . 'replies/capabilities.php';
+		require $this->includes_dir . 'replies/functions.php';
+		require $this->includes_dir . 'replies/template.php';
+
+		// Search
+		require $this->includes_dir . 'search/functions.php';
+		require $this->includes_dir . 'search/template.php';
+
+		// Users
+		require $this->includes_dir . 'users/capabilities.php';
+		require $this->includes_dir . 'users/engagements.php';
+		require $this->includes_dir . 'users/functions.php';
+		require $this->includes_dir . 'users/template.php';
+		require $this->includes_dir . 'users/options.php';
+		require $this->includes_dir . 'users/signups.php';
+
+		/** Hooks *************************************************************/
+
+		require $this->includes_dir . 'core/extend.php';
+		require $this->includes_dir . 'core/actions.php';
+		require $this->includes_dir . 'core/filters.php';
+
+		/** Admin *************************************************************/
+
+		// Quick admin check and load if needed
+		if ( is_admin() ) {
+			require $this->includes_dir . 'admin/actions.php';
+		}
+	}
+
+	/**
+	 * Setup the default hooks and actions
+	 *
+	 * @since 2.0.0 bbPress (r2644)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Add actions to plugin activation and deactivation hooks
+		add_action( 'activate_'   . $this->basename, 'bbp_activation'   );
+		add_action( 'deactivate_' . $this->basename, 'bbp_deactivation' );
+
+		// If bbPress is being deactivated, do not add any actions
+		if ( bbp_is_deactivation( $this->basename ) ) {
+			return;
+		}
+
+		// Array of bbPress core actions
+		$actions = array(
+			'setup_theme',              // Setup the default theme compat
+			'setup_current_user',       // Setup currently logged in user
+			'setup_engagements',        // Setup user engagements strategy
+			'roles_init',               // User roles init
+			'register_meta',            // Register meta (forum|topic|reply|user)
+			'register_post_types',      // Register post types (forum|topic|reply)
+			'register_post_statuses',   // Register post statuses (closed|spam|orphan|hidden)
+			'register_taxonomies',      // Register taxonomies (topic-tag)
+			'register_shortcodes',      // Register shortcodes (bbp-login)
+			'register_views',           // Register the views (no-replies)
+			'register_theme_packages',  // Register bundled theme packages (bbp-theme-compat/bbp-themes)
+			'load_textdomain',          // Load textdomain (bbpress)
+			'add_rewrite_tags',         // Add rewrite tags (view|user|edit|search)
+			'add_rewrite_rules',        // Generate rewrite rules (view|edit|paged|search)
+			'add_permastructs'          // Add permalink structures (view|user|search)
+		);
+
+		// Add the actions
+		foreach ( $actions as $class_action ) {
+			add_action( 'bbp_' . $class_action, array( $this, $class_action ), 5 );
+		}
+
+		// All bbPress actions are setup (includes bbp-core-hooks.php)
+		do_action_ref_array( 'bbp_after_setup_actions', array( &$this ) );
+	}
+
+	/** Public Methods ********************************************************/
+
+	/**
+	 * Register bundled theme packages
+	 *
+	 * Note that since we currently have complete control over bbp-themes and
+	 * the bbp-theme-compat folders, it's fine to hardcode these here. If at a
+	 * later date we need to automate this, and API will need to be built.
+	 *
+	 * @since 2.1.0 bbPress (r3829)
+	 */
+	public function register_theme_packages() {
+
+		// Register the basic theme stack. This is really dope.
+		bbp_register_template_stack( 'get_stylesheet_directory', 6 );
+		bbp_register_template_stack( 'get_template_directory',   8 );
+
+		// Register the default theme compatibility package
+		bbp_register_theme_package( array(
+			'id'      => 'default',
+			'name'    => 'bbPress Default',
+			'version' => bbp_get_version(),
+			'dir'     => trailingslashit( $this->themes_dir . 'default' ),
+			'url'     => trailingslashit( $this->themes_url . 'default' )
+		) );
+	}
+
+	/**
+	 * Setup the default bbPress theme compatibility location.
+	 *
+	 * @since 2.1.0 bbPress (r3778)
+	 */
+	public function setup_theme() {
+		bbp_setup_theme_compat( bbp_get_theme_package_id() );
+	}
+
+	/**
+	 * Load the translation file for current language. Checks the deprecated
+	 * languages folder inside the bbPress plugin first, and then the default
+	 * WordPress languages folder.
+	 *
+	 * Note that custom translation files inside the bbPress plugin folder
+	 * will be removed on bbPress updates. If you're creating custom
+	 * translation files, please use the global language folder.
+	 *
+	 * @since 2.0.0 bbPress (r2596)
+	 */
+	public function load_textdomain() {
+
+		// Define the old directory
+		$old_dir = WP_LANG_DIR . '/bbpress/';
+
+		// Old location, deprecated in 2.6.0
+		if ( is_dir( $old_dir ) ) {
+
+			// Get locale & file-name
+			$type   = is_admin() ? get_user_locale() : get_locale();
+			$locale = apply_filters( 'plugin_locale', $type, $this->domain );
+			$mofile = sprintf( '%1$s-%2$s.mo', $this->domain, $locale );
+
+			// Look in global /wp-content/languages/bbpress/ folder
+			load_textdomain( $this->domain, $old_dir . $mofile );
+		}
+
+		// Look in global /wp-content/languages/plugins/
+		load_plugin_textdomain( $this->domain, false, $this->lang_base );
+	}
+
+	/**
+	 * Setup the post types for forums, topics and replies
+	 *
+	 * @since 2.0.0 bbPress (r2597)
+	 */
+	public static function register_post_types() {
+
+		/** Forums ************************************************************/
+
+		// Register Forum content type
+		register_post_type(
+			bbp_get_forum_post_type(),
+			apply_filters( 'bbp_register_forum_post_type', array(
+				'labels'              => bbp_get_forum_post_type_labels(),
+				'rewrite'             => bbp_get_forum_post_type_rewrite(),
+				'supports'            => bbp_get_forum_post_type_supports(),
+				'description'         => esc_html__( 'bbPress Forums', 'bbpress' ),
+				'capabilities'        => bbp_get_forum_caps(),
+				'capability_type'     => array( 'forum', 'forums' ),
+				'menu_position'       => 555555,
+				'has_archive'         => bbp_get_root_slug(),
+				'exclude_from_search' => true,
+				'show_in_nav_menus'   => true,
+				'public'              => true,
+				'show_ui'             => current_user_can( 'bbp_forums_admin' ),
+				'can_export'          => true,
+				'hierarchical'        => true,
+				'query_var'           => true,
+				'menu_icon'           => '',
+				'source'              => 'bbpress',
+			) )
+		);
+
+		/** Topics ************************************************************/
+
+		// Register Topic content type
+		register_post_type(
+			bbp_get_topic_post_type(),
+			apply_filters( 'bbp_register_topic_post_type', array(
+				'labels'              => bbp_get_topic_post_type_labels(),
+				'rewrite'             => bbp_get_topic_post_type_rewrite(),
+				'supports'            => bbp_get_topic_post_type_supports(),
+				'description'         => esc_html__( 'bbPress Topics', 'bbpress' ),
+				'capabilities'        => bbp_get_topic_caps(),
+				'capability_type'     => array( 'topic', 'topics' ),
+				'menu_position'       => 555555,
+				'has_archive'         => ( 'forums' === bbp_show_on_root() ) ? bbp_get_topic_archive_slug() : false,
+				'exclude_from_search' => true,
+				'show_in_nav_menus'   => false,
+				'public'              => true,
+				'show_ui'             => current_user_can( 'bbp_topics_admin' ),
+				'can_export'          => true,
+				'hierarchical'        => false,
+				'query_var'           => true,
+				'menu_icon'           => '',
+				'source'              => 'bbpress',
+			)
+		) );
+
+		/** Replies ***********************************************************/
+
+		// Register reply content type
+		register_post_type(
+			bbp_get_reply_post_type(),
+			apply_filters( 'bbp_register_reply_post_type', array(
+				'labels'              => bbp_get_reply_post_type_labels(),
+				'rewrite'             => bbp_get_reply_post_type_rewrite(),
+				'supports'            => bbp_get_reply_post_type_supports(),
+				'description'         => esc_html__( 'bbPress Replies', 'bbpress' ),
+				'capabilities'        => bbp_get_reply_caps(),
+				'capability_type'     => array( 'reply', 'replies' ),
+				'menu_position'       => 555555,
+				'exclude_from_search' => true,
+				'has_archive'         => false,
+				'show_in_nav_menus'   => false,
+				'public'              => true,
+				'show_ui'             => current_user_can( 'bbp_replies_admin' ),
+				'can_export'          => true,
+				'hierarchical'        => false,
+				'query_var'           => true,
+				'menu_icon'           => '',
+				'source'              => 'bbpress',
+			) )
+		);
+	}
+
+	/**
+	 * Register the post statuses used by bbPress
+	 *
+	 * We do some manipulation of the 'trash' status so trashed topics and
+	 * replies can be viewed from within the theme.
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 */
+	public static function register_post_statuses() {
+
+		// Closed
+		register_post_status(
+			bbp_get_closed_status_id(),
+			apply_filters( 'bbp_register_closed_post_status', array(
+				'label'                     => _x( 'Closed', 'post', 'bbpress' ),
+				'label_count'               => _nx_noop( 'Closed <span class="count">(%s)</span>', 'Closed <span class="count">(%s)</span>', 'post', 'bbpress' ),
+				'public'                    => true,
+				'show_in_admin_status_list' => true,
+				'show_in_admin_all_list'    => true,
+				'source'                    => 'bbpress'
+			) )
+		);
+
+		// Spam
+		register_post_status(
+			bbp_get_spam_status_id(),
+			apply_filters( 'bbp_register_spam_post_status', array(
+				'label'                     => _x( 'Spam', 'post', 'bbpress' ),
+				'label_count'               => _nx_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'post', 'bbpress' ),
+				'protected'                 => true,
+				'exclude_from_search'       => true,
+				'show_in_admin_status_list' => true,
+				'show_in_admin_all_list'    => false,
+				'source'                    => 'bbpress'
+			) )
+		);
+
+		// Orphan
+		register_post_status(
+			bbp_get_orphan_status_id(),
+			apply_filters( 'bbp_register_orphan_post_status', array(
+				'label'                     => _x( 'Orphan', 'post', 'bbpress' ),
+				'label_count'               => _nx_noop( 'Orphan <span class="count">(%s)</span>', 'Orphans <span class="count">(%s)</span>', 'post', 'bbpress' ),
+				'protected'                 => true,
+				'exclude_from_search'       => true,
+				'show_in_admin_status_list' => true,
+				'show_in_admin_all_list'    => false,
+				'source'                    => 'bbpress'
+			) )
+		);
+
+		// Hidden
+		register_post_status(
+			bbp_get_hidden_status_id(),
+			apply_filters( 'bbp_register_hidden_post_status', array(
+				'label'                     => _x( 'Hidden', 'post', 'bbpress' ),
+				'label_count'               => _nx_noop( 'Hidden <span class="count">(%s)</span>', 'Hidden <span class="count">(%s)</span>', 'post', 'bbpress' ),
+				'private'                   => true,
+				'exclude_from_search'       => true,
+				'show_in_admin_status_list' => true,
+				'show_in_admin_all_list'    => true,
+				'source'                    => 'bbpress'
+			) )
+		);
+
+		/**
+		 * Trash fix
+		 *
+		 * We need to remove the internal arg and change that to
+		 * protected so that the users with 'view_trash' cap can view
+		 * single trashed topics/replies in the front-end as wp_query
+		 * doesn't allow any hack for the trashed topics to be viewed.
+		 */
+		global $wp_post_statuses;
+
+		if ( ! empty( $wp_post_statuses['trash'] ) ) {
+
+			// User can view trash so set internal to false
+			if ( current_user_can( 'view_trash' ) ) {
+				$wp_post_statuses['trash']->internal  = false;
+				$wp_post_statuses['trash']->protected = true;
+
+			// User cannot view trash so set internal to true
+			} else {
+				$wp_post_statuses['trash']->internal = true;
+			}
+		}
+	}
+
+	/**
+	 * Register the topic tag and forum moderator taxonomies
+	 *
+	 * @since 2.0.0 bbPress (r2464) Added bbp_get_topic_tag_tax_id() taxonomy
+	 */
+	public static function register_taxonomies() {
+
+		// Register the topic-tag taxonomy.
+		register_taxonomy(
+			bbp_get_topic_tag_tax_id(),
+			bbp_get_topic_post_type(),
+			apply_filters( 'bbp_register_topic_taxonomy', array(
+				'labels'                => bbp_get_topic_tag_tax_labels(),
+				'rewrite'               => bbp_get_topic_tag_tax_rewrite(),
+				'capabilities'          => bbp_get_topic_tag_caps(),
+				'update_count_callback' => 'bbp_update_topic_tag_count',
+				'query_var'             => true,
+				'show_tagcloud'         => true,
+				'hierarchical'          => false,
+				'show_in_nav_menus'     => false,
+				'public'                => true,
+				'show_ui'               => bbp_allow_topic_tags() && current_user_can( 'bbp_topic_tags_admin' ),
+				'source'                => 'bbpress'
+			)
+		) );
+	}
+
+	/**
+	 * Register the bbPress views
+	 *
+	 * @since 2.0.0 bbPress (r2789)
+	 */
+	public static function register_views() {
+
+		// Popular topics
+		bbp_register_view(
+			'popular',
+			esc_html__( 'Most popular topics', 'bbpress' ),
+			apply_filters( 'bbp_register_view_popular', array(
+				'meta_key'      => '_bbp_reply_count',
+				'meta_type'     => 'NUMERIC',
+				'max_num_pages' => 1,
+				'orderby'       => 'meta_value_num',
+				'show_stickies' => false
+			)
+		) );
+
+		// Topics with no replies
+		bbp_register_view(
+			'no-replies',
+			esc_html__( 'Topics with no replies', 'bbpress' ),
+			apply_filters( 'bbp_register_view_no_replies', array(
+				'meta_key'      => '_bbp_reply_count',
+				'meta_type'     => 'NUMERIC',
+				'meta_value'    => 1,
+				'meta_compare'  => '<',
+				'orderby'       => '',
+				'show_stickies' => false
+			)
+		) );
+	}
+
+	/**
+	 * Register the bbPress shortcodes
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 */
+	public function register_shortcodes() {
+		$this->shortcodes = new BBP_Shortcodes();
+	}
+
+	/**
+	 * Register bbPress meta-data
+	 *
+	 * Counts added in 2.6.0 to avoid negative values
+	 *
+	 * @since 2.6.0 bbPress (r6300)
+	 */
+	public function register_meta() {
+
+		// Define "count" meta-type array
+		$count = array(
+
+			// Counts are always integers
+			'type'              => 'integer',
+
+			// Generic count description
+			'description'       => esc_html__( 'bbPress Item Count', 'bbpress' ),
+
+			// Counts are single values
+			'single'            => true,
+
+			// Counts should be made available in REST
+			'show_in_rest'      => true,
+
+			// Never allow counts to go negative
+			'sanitize_callback' => 'bbp_number_not_negative',
+
+			// All users may update count meta data
+			'auth_callback'     => '__return_true'
+		);
+
+		/** Post **************************************************************/
+
+		// Counts
+		register_meta( 'post', '_bbp_topic_count',           $count );
+		register_meta( 'post', '_bbp_reply_count',           $count );
+		register_meta( 'post', '_bbp_total_topic_count',     $count );
+		register_meta( 'post', '_bbp_total_reply_count',     $count );
+		register_meta( 'post', '_bbp_voice_count',           $count );
+		register_meta( 'post', '_bbp_anonymous_reply_count', $count );
+		register_meta( 'post', '_bbp_topic_count_hidden',    $count );
+		register_meta( 'post', '_bbp_reply_count_hidden',    $count );
+		register_meta( 'post', '_bbp_forum_subforum_count',  $count );
+
+		/* User ***************************************************************/
+
+		// Counts
+		register_meta( 'user', '_bbp_topic_count', $count );
+		register_meta( 'user', '_bbp_reply_count', $count );
+
+		// Activity
+		register_meta( 'user', '_bbp_last_posted', array(
+			'type'              => 'integer',
+			'description'       => esc_html__( 'bbPress User Activity', 'bbpress' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'bbp_number_not_negative',
+			'auth_callback'     => '__return_true'
+		) );
+	}
+
+	/**
+	 * Setup the currently logged-in user
+	 *
+	 * @since 2.0.0 bbPress (r2697)
+	 */
+	public function setup_current_user() {
+		$this->current_user = wp_get_current_user();
+	}
+
+	/**
+	 * Setup the user engagements strategy
+	 *
+	 * @since 2.6.0 bbPress (r6875)
+	 */
+	public function setup_engagements() {
+
+		// Setup the class name
+		$strategy   = ucwords( bbp_engagements_strategy() );
+		$class_name = "BBP_User_Engagements_{$strategy}";
+
+		// Setup the engagements interface
+		$this->engagements = new $class_name();
+	}
+
+	/**
+	 * Initialize forum-specific roles
+	 *
+	 * @since 2.6.0
+	 */
+	public function roles_init() {
+
+		// Get role IDs
+		$keymaster   = bbp_get_keymaster_role();
+		$moderator   = bbp_get_moderator_role();
+		$participant = bbp_get_participant_role();
+		$spectator   = bbp_get_spectator_role();
+		$blocked     = bbp_get_blocked_role();
+
+		// Build the roles into one useful array
+		$this->roles[ $keymaster   ] = new WP_Role( 'Keymaster',   bbp_get_caps_for_role( $keymaster   ) );
+		$this->roles[ $moderator   ] = new WP_Role( 'Moderator',   bbp_get_caps_for_role( $moderator   ) );
+		$this->roles[ $participant ] = new WP_Role( 'Participant', bbp_get_caps_for_role( $participant ) );
+		$this->roles[ $spectator   ] = new WP_Role( 'Spectator',   bbp_get_caps_for_role( $spectator   ) );
+		$this->roles[ $blocked     ] = new WP_Role( 'Blocked',     bbp_get_caps_for_role( $blocked     ) );
+	}
+
+	/** Custom Rewrite Rules **************************************************/
+
+	/**
+	 * Add the bbPress-specific rewrite tags
+	 *
+	 * @since 2.0.0 bbPress (r2753)
+	 */
+	public static function add_rewrite_tags() {
+		add_rewrite_tag( '%' . bbp_get_view_rewrite_id()               . '%', '([^/]+)'   ); // View Page tag
+		add_rewrite_tag( '%' . bbp_get_edit_rewrite_id()               . '%', '([1]{1,})' ); // Edit Page tag
+		add_rewrite_tag( '%' . bbp_get_search_rewrite_id()             . '%', '([^/]+)'   ); // Search Results tag
+		add_rewrite_tag( '%' . bbp_get_user_rewrite_id()               . '%', '([^/]+)'   ); // User Profile tag
+		add_rewrite_tag( '%' . bbp_get_user_favorites_rewrite_id()     . '%', '([1]{1,})' ); // User Favorites tag
+		add_rewrite_tag( '%' . bbp_get_user_subscriptions_rewrite_id() . '%', '([1]{1,})' ); // User Subscriptions tag
+		add_rewrite_tag( '%' . bbp_get_user_engagements_rewrite_id()   . '%', '([1]{1,})' ); // User Engagements tag
+		add_rewrite_tag( '%' . bbp_get_user_topics_rewrite_id()        . '%', '([1]{1,})' ); // User Topics Tag
+		add_rewrite_tag( '%' . bbp_get_user_replies_rewrite_id()       . '%', '([1]{1,})' ); // User Replies Tag
+	}
+
+	/**
+	 * Add bbPress-specific rewrite rules for uri's that are not
+	 * setup for us by way of custom post types or taxonomies. This includes:
+	 * - Front-end editing
+	 * - Topic views
+	 * - User profiles
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @todo Extract into an API
+	 */
+	public static function add_rewrite_rules() {
+
+		/** Setup *************************************************************/
+
+		// Add rules to top or bottom?
+		$priority           = 'top';
+
+		// Single Slugs
+		$forum_slug         = bbp_get_forum_slug();
+		$topic_slug         = bbp_get_topic_slug();
+		$reply_slug         = bbp_get_reply_slug();
+		$ttag_slug          = bbp_get_topic_tag_tax_slug();
+
+		// Archive Slugs
+		$user_slug          = bbp_get_user_slug();
+		$view_slug          = bbp_get_view_slug();
+		$search_slug        = bbp_get_search_slug();
+		$topic_archive_slug = bbp_get_topic_archive_slug();
+		$reply_archive_slug = bbp_get_reply_archive_slug();
+
+		// Tertiary Slugs
+		$feed_slug          = 'feed';
+		$edit_slug          = bbp_get_edit_slug();
+		$paged_slug         = bbp_get_paged_slug();
+		$user_favs_slug     = bbp_get_user_favorites_slug();
+		$user_subs_slug     = bbp_get_user_subscriptions_slug();
+		$user_engs_slug     = bbp_get_user_engagements_slug();
+
+		// Unique rewrite ID's
+		$feed_id            = 'feed';
+		$edit_id            = bbp_get_edit_rewrite_id();
+		$view_id            = bbp_get_view_rewrite_id();
+		$paged_id           = bbp_get_paged_rewrite_id();
+		$search_id          = bbp_get_search_rewrite_id();
+		$user_id            = bbp_get_user_rewrite_id();
+		$user_favs_id       = bbp_get_user_favorites_rewrite_id();
+		$user_subs_id       = bbp_get_user_subscriptions_rewrite_id();
+		$user_tops_id       = bbp_get_user_topics_rewrite_id();
+		$user_reps_id       = bbp_get_user_replies_rewrite_id();
+		$user_engs_id       = bbp_get_user_engagements_rewrite_id();
+
+		// Rewrite rule matches used repeatedly below
+		$root_rule    = '/([^/]+)/?$';
+		$feed_rule    = '/([^/]+)/' . $feed_slug  . '/?$';
+		$edit_rule    = '/([^/]+)/' . $edit_slug  . '/?$';
+		$paged_rule   = '/([^/]+)/' . $paged_slug . '/?([0-9]{1,})/?$';
+
+		// Search rules (without slug check)
+		$search_root_rule  = '/?$';
+		$search_paged_rule = '/' . $paged_slug . '/?([0-9]{1,})/?$';
+
+		/** Add ***************************************************************/
+
+		// User profile rules
+		$tops_rule       = '/([^/]+)/' . $topic_archive_slug . '/?$';
+		$reps_rule       = '/([^/]+)/' . $reply_archive_slug . '/?$';
+		$favs_rule       = '/([^/]+)/' . $user_favs_slug     . '/?$';
+		$subs_rule       = '/([^/]+)/' . $user_subs_slug     . '/?$';
+		$engs_rule       = '/([^/]+)/' . $user_engs_slug     . '/?$';
+		$tops_paged_rule = '/([^/]+)/' . $topic_archive_slug . '/' . $paged_slug . '/?([0-9]{1,})/?$';
+		$reps_paged_rule = '/([^/]+)/' . $reply_archive_slug . '/' . $paged_slug . '/?([0-9]{1,})/?$';
+		$favs_paged_rule = '/([^/]+)/' . $user_favs_slug     . '/' . $paged_slug . '/?([0-9]{1,})/?$';
+		$subs_paged_rule = '/([^/]+)/' . $user_subs_slug     . '/' . $paged_slug . '/?([0-9]{1,})/?$';
+		$engs_paged_rule = '/([^/]+)/' . $user_engs_slug     . '/' . $paged_slug . '/?([0-9]{1,})/?$';
+
+		// Edit Forum|Topic|Reply|Topic-tag
+		add_rewrite_rule( $forum_slug . $edit_rule, 'index.php?' . bbp_get_forum_post_type()  . '=$matches[1]&' . $edit_id . '=1', $priority );
+		add_rewrite_rule( $topic_slug . $edit_rule, 'index.php?' . bbp_get_topic_post_type()  . '=$matches[1]&' . $edit_id . '=1', $priority );
+		add_rewrite_rule( $reply_slug . $edit_rule, 'index.php?' . bbp_get_reply_post_type()  . '=$matches[1]&' . $edit_id . '=1', $priority );
+		add_rewrite_rule( $ttag_slug  . $edit_rule, 'index.php?' . bbp_get_topic_tag_tax_id() . '=$matches[1]&' . $edit_id . '=1', $priority );
+
+		// User Pagination|Edit|View
+		add_rewrite_rule( $user_slug . $tops_paged_rule, 'index.php?' . $user_id  . '=$matches[1]&' . $user_tops_id . '=1&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $user_slug . $reps_paged_rule, 'index.php?' . $user_id  . '=$matches[1]&' . $user_reps_id . '=1&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $user_slug . $favs_paged_rule, 'index.php?' . $user_id  . '=$matches[1]&' . $user_favs_id . '=1&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $user_slug . $subs_paged_rule, 'index.php?' . $user_id  . '=$matches[1]&' . $user_subs_id . '=1&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $user_slug . $engs_paged_rule, 'index.php?' . $user_id  . '=$matches[1]&' . $user_engs_id . '=1&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $user_slug . $tops_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $user_tops_id . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $reps_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $user_reps_id . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $favs_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $user_favs_id . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $subs_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $user_subs_id . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $engs_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $user_engs_id . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $edit_rule,       'index.php?' . $user_id  . '=$matches[1]&' . $edit_id      . '=1',                               $priority );
+		add_rewrite_rule( $user_slug . $root_rule,       'index.php?' . $user_id  . '=$matches[1]',                                                       $priority );
+
+		// Topic-View Pagination|Feed|View
+		add_rewrite_rule( $view_slug . $paged_rule, 'index.php?' . $view_id . '=$matches[1]&' . $paged_id . '=$matches[2]', $priority );
+		add_rewrite_rule( $view_slug . $feed_rule,  'index.php?' . $view_id . '=$matches[1]&' . $feed_id  . '=$matches[2]', $priority );
+		add_rewrite_rule( $view_slug . $root_rule,  'index.php?' . $view_id . '=$matches[1]',                               $priority );
+
+		// Search All
+		add_rewrite_rule( $search_slug . $search_paged_rule, 'index.php?' . $paged_id .'=$matches[1]', $priority );
+		add_rewrite_rule( $search_slug . $search_root_rule,  'index.php?' . $search_id,                $priority );
+	}
+
+	/**
+	 * Add permalink structures for new archive-style destinations.
+	 *
+	 * - Users
+	 * - Topic Views
+	 * - Search
+	 *
+	 * @since 2.4.0 bbPress (r4930)
+	 */
+	public static function add_permastructs() {
+
+		// Get unique ID's
+		$user_id     = bbp_get_user_rewrite_id();
+		$view_id     = bbp_get_view_rewrite_id();
+		$search_id   = bbp_get_search_rewrite_id();
+
+		// Get root slugs
+		$user_slug   = bbp_get_user_slug();
+		$view_slug   = bbp_get_view_slug();
+		$search_slug = bbp_get_search_slug();
+
+		// User Permastruct
+		add_permastruct( $user_id, $user_slug . '/%' . $user_id . '%', array(
+			'with_front'  => false,
+			'ep_mask'     => EP_NONE,
+			'paged'       => false,
+			'feed'        => false,
+			'forcomments' => false,
+			'walk_dirs'   => true,
+			'endpoints'   => false,
+		) );
+
+		// Topic View Permastruct
+		add_permastruct( $view_id, $view_slug . '/%' . $view_id . '%', array(
+			'with_front'  => false,
+			'ep_mask'     => EP_NONE,
+			'paged'       => false,
+			'feed'        => false,
+			'forcomments' => false,
+			'walk_dirs'   => true,
+			'endpoints'   => false,
+		) );
+
+		// Search Permastruct
+		add_permastruct( $search_id, $search_slug . '/%' . $search_id . '%', array(
+			'with_front'  => false,
+			'ep_mask'     => EP_NONE,
+			'paged'       => true,
+			'feed'        => false,
+			'forcomments' => false,
+			'walk_dirs'   => true,
+			'endpoints'   => false,
+		) );
+	}
+}
+
+/**
+ * The main function responsible for returning the one true bbPress Instance
+ * to functions everywhere.
+ *
+ * Use this function like you would a global variable, except without needing
+ * to declare the global.
+ *
+ * Example: <?php $bbp = bbpress(); ?>
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @return bbPress The one true bbPress Instance
+ */
+function bbpress() {
+	return bbPress::instance();
+}
+
+/**
+ * Hook bbPress early onto the 'plugins_loaded' action.
+ *
+ * This gives all other plugins the chance to load before bbPress, to get their
+ * actions, filters, and overrides setup without bbPress being in the way.
+ */
+if ( defined( 'BBPRESS_LATE_LOAD' ) ) {
+	add_action( 'plugins_loaded', 'bbpress', (int) BBPRESS_LATE_LOAD );
+
+// "And now here's something we hope you'll really like!"
+} else {
+	bbpress();
+}
+
+endif; // class_exists check
Index: /tags/2.6.6/src/composer.json
===================================================================
--- /tags/2.6.6/src/composer.json	(revision 7162)
+++ /tags/2.6.6/src/composer.json	(revision 7162)
@@ -0,0 +1,29 @@
+{
+	"name": "bbpress/bbpress",
+	"description": "bbPress is forum software with a twist from the creators of WordPress.",
+	"type": "wordpress-plugin",
+	"keywords": [
+		"bulletin board",
+		"community",
+		"forum",
+		"support",
+		"wordpress"
+	],
+	"homepage": "https://bbpress.org",
+	"license": "GPL-2.0-or-later",
+	"authors": [ {
+		"name": "bbPress Contributors",
+		"homepage": "https://bbpress.org/about/"
+	} ],
+	"support": {
+		"forum": "https://bbpress.org/support/",
+		"irc": "irc://irc.freenode.net/bbpress-dev",
+		"issues": "https://bbpress.trac.wordpress.org/",
+		"wiki": "https://codex.bbpress.org/",
+		"source": "https://bbpress.trac.wordpress.org/browser"
+	},
+	"require": {
+		"composer/installers": "~1.0",
+		"php": ">=5.6.20"
+	}
+}
Index: /tags/2.6.6/src/humans.txt
===================================================================
--- /tags/2.6.6/src/humans.txt	(revision 7162)
+++ /tags/2.6.6/src/humans.txt	(revision 7162)
@@ -0,0 +1,59 @@
+                ..=OO$,
+            ~OO=. . . . .Z88
+        .78..  .,OOOOOOO    .7O
+       IO.  7OOOOOOOOOOOOOOO.   8..
+      O. .OOOOOOOOOOOOOOOOOOOOO   O.
+    7O  OOOOOOOOOOOOOOOOOOOOOOOO8  O.
+   ,O.      OOOOOOO..     OOOOOOOO. O,
+  IO  OO    OOOOOOOOO.   OOOOOOOOOO..O
+ .O .8OO    OOOOOOOOO.   OOOOOOOOOOO  O
+ O  OOOO   8OOOOOOOOO.   OOOOOOOOOOOZ ,O
+ O .OOO.  .OOOOOOOOOO   OOOOOOOOOOOOO .8
+.Z 7OOO   .OOOOOOOOO   .OOOOOOOOOOOOO  8.
+.O OOOO.  OOO~..ZOOO.  .OOO. +OOOOOOO: O.
+.O OOO.     .OI.  .$   Z..7O.  .OOOOO~ O.
+.Z $OO   .ZOOOOOO     ..OOOOOO   OOOO  O.
+ O  O7   OOOOOOOO     .OOOOOOO   IOOO  O
+ O  8   .OOOOOOOO     OOOOOOOO   OOOZ .O
+  8     .OOOOOOOO     OOOOOOOO   OOO .O.
+  ?O    OOOOOOOO      OOOOOOO.  .OO..O.
+   O+   OOOOOOO      +OOOOOO? ..OO  O,
+    78   OOOOI .,O8   OOOO8. .OO8 .O
+      O.  . . OOOOOO7..  ..OOOO  .8
+      .?O  .7OOOOOOOOOOOOOOO.. .8.
+         78    .,OOOOOOO.    7O..
+            :OO+.       .ZO8.
+                ..=OO$,
+
+    We are the humans behind bbPress
+
+/* TEAM */
+Name: Matt Mullenweg
+Title: Founding Developer
+Twitter: photomatt
+Favorite Food: BBQ
+
+Name: John James Jacoby
+Title: Lead Developer
+Twitter: jjj
+Favorite Food: Truffle Lasagna
+
+Name: Jennifer M. Dodd
+Title: Core Contributor
+Twitter: jmdodd
+
+Name: Stephen Edgar
+Title: Core Contributor
+Twitter: netweb
+Favorite Food: Tacos 🌮
+
+Name: Sergey Biryukov
+Title: Core Contributor
+Twitter: sergeybiryukov
+
+/* THANKS */
+Mike Adams, Gautam Gupta, Ben L, Justin Tadlock, Pippin Williamson, cnorris23, anointed, andy, nacin
+
+/* META */
+Updated: 2017/09/09
+See: http://humanstxt.org/
Index: /tags/2.6.6/src/includes/admin/actions.php
===================================================================
--- /tags/2.6.6/src/includes/admin/actions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/actions.php	(revision 7162)
@@ -0,0 +1,344 @@
+<?php
+
+/**
+ * bbPress Admin Actions
+ *
+ * This file contains the actions that are used through-out bbPress Admin. They
+ * are consolidated here to make searching for them easier, and to help developers
+ * understand at a glance the order in which things occur.
+ *
+ * There are a few common places that additional actions can currently be found
+ *
+ *  - bbPress: In {@link bbPress::setup_actions()} in bbpress.php
+ *  - Admin: More in {@link BBP_Admin::setup_actions()} in class-bbp-admin.php
+ *
+ * @package bbPress
+ * @subpackage Administration
+ *
+ * @see bbp-core-actions.php
+ * @see bbp-core-filters.php
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach bbPress to WordPress
+ *
+ * bbPress uses its own internal actions to help aid in third-party plugin
+ * development, and to limit the amount of potential future code changes when
+ * updates to WordPress core occur.
+ *
+ * These actions exist to create the concept of 'plugin dependencies'. They
+ * provide a safe way for plugins to execute code *only* when bbPress is
+ * installed and activated, without needing to do complicated guesswork.
+ *
+ * For more information on how this works, see the 'Plugin Dependency' section
+ * near the bottom of this file.
+ *
+ *           v--WordPress Actions       v--bbPress Sub-actions
+ */
+add_action( 'wpmu_new_blog',           'bbp_new_site',               10, 6 );
+add_action( 'current_screen',          'bbp_current_screen'                );
+add_action( 'tool_box',                'bbp_admin_tool_box'                );
+add_action( 'admin_menu',              'bbp_admin_menu'                    );
+add_action( 'admin_init',              'bbp_admin_init'                    );
+add_action( 'admin_head',              'bbp_admin_head'                    );
+add_action( 'admin_notices',           'bbp_admin_notices'                 );
+add_action( 'menu_order',              'bbp_admin_menu_order'              );
+add_filter( 'custom_menu_order',       'bbp_admin_custom_menu_order'       );
+
+// Hook on to admin_init
+add_action( 'bbp_admin_init', 'bbp_setup_updater',          999 );
+add_action( 'bbp_admin_init', 'bbp_register_importers'          );
+add_action( 'bbp_admin_init', 'bbp_register_admin_styles'       );
+add_action( 'bbp_admin_init', 'bbp_register_admin_scripts'      );
+add_action( 'bbp_admin_init', 'bbp_register_admin_settings'     );
+
+// Hook on to current_screen (only in Site admin, not Network or User)
+if ( is_blog_admin() ) {
+	add_action( 'bbp_current_screen', 'bbp_admin_forums'  );
+	add_action( 'bbp_current_screen', 'bbp_admin_topics'  );
+	add_action( 'bbp_current_screen', 'bbp_admin_replies' );
+}
+
+// Initialize the admin area
+add_action( 'bbp_init', 'bbp_setup_admin' );
+
+// Reset the menu order
+add_action( 'bbp_admin_menu', 'bbp_admin_separator' );
+
+// Activation
+add_action( 'bbp_activation',   'bbp_setup_new_site'              );
+add_action( 'bbp_activation',   'bbp_add_activation_redirect'     );
+add_action( 'bbp_activation',   'bbp_delete_rewrite_rules'        );
+add_action( 'bbp_activation',   'bbp_make_current_user_keymaster' );
+add_action( 'load-plugins.php', 'bbp_do_activation_redirect'      );
+
+// Deactivation
+add_action( 'bbp_deactivation', 'bbp_remove_caps'          );
+add_action( 'bbp_deactivation', 'bbp_delete_rewrite_rules' );
+
+// New Site
+add_action( 'bbp_new_site', 'bbp_setup_new_site', 8 );
+
+// Load the default repair tools
+add_action( 'load-tools_page_bbp-repair',  'bbp_register_default_repair_tools' );
+add_action( 'load-tools_page_bbp-upgrade', 'bbp_register_default_repair_tools' );
+
+// Contextual Helpers
+add_action( 'load-settings_page_bbpress',    'bbp_admin_settings_help'        );
+add_action( 'load-tools_page_bbp-repair',    'bbp_admin_tools_repair_help'    );
+add_action( 'load-tools_page_bbp-upgrade',   'bbp_admin_tools_repair_help'    );
+add_action( 'load-tools_page_bbp-converter', 'bbp_admin_tools_converter_help' );
+add_action( 'load-tools_page_bbp-reset',     'bbp_admin_tools_reset_help'     );
+
+// Handle submission of Tools pages
+add_action( 'load-tools_page_bbp-repair',  'bbp_admin_repair_handler' );
+add_action( 'load-tools_page_bbp-upgrade', 'bbp_admin_repair_handler' );
+add_action( 'load-tools_page_bbp-reset',   'bbp_admin_reset_handler'  );
+add_action( 'bbp_admin_tool_box',          'bbp_admin_tools_box'      );
+
+// Add sample permalink filter
+add_filter( 'post_type_link', 'bbp_filter_sample_permalink', 10, 4 );
+
+// Add quick stats to dashboard glance elements
+add_filter( 'dashboard_glance_items', 'bbp_filter_dashboard_glance_items', -99 );
+
+// Maybe use icons for column headers
+add_filter( 'bbp_admin_forums_column_headers',  'bbp_filter_column_headers' );
+add_filter( 'bbp_admin_topics_column_headers',  'bbp_filter_column_headers' );
+add_filter( 'bbp_admin_replies_column_headers', 'bbp_filter_column_headers' );
+
+// Load the converter early (page and AJAX)
+add_action( 'load-tools_page_bbp-converter', 'bbp_setup_converter', 2 );
+add_action( 'wp_ajax_bbp_converter_process', 'bbp_setup_converter', 2 );
+
+// Add New User
+add_action( 'user_new_form', 'bbp_add_user_form_role_field', 10, 1 );
+
+/**
+ * Setup bbPress admin
+ *
+ * @since 2.0.0 bbPress (r1000)
+ * @since 2.6.0 bbPress (r6598) Moved to actions.php
+ */
+function bbp_admin() {
+	return bbp_setup_admin();
+}
+
+/**
+ * When a new site is created in a multisite installation, run the activation
+ * routine on that site
+ *
+ * @since 2.0.0 bbPress (r3283)
+ *
+ * @param int $blog_id
+ * @param int $user_id
+ * @param string $domain
+ * @param string $path
+ * @param int $site_id
+ * @param array() $meta
+ */
+function bbp_new_site( $blog_id, $user_id, $domain, $path, $site_id, $meta ) {
+
+	// Bail if plugin is not network activated
+	if ( ! is_plugin_active_for_network( bbpress()->basename ) ) {
+		return;
+	}
+
+	// Switch to the new site
+	bbp_switch_to_site( $blog_id );
+
+	// Do the bbPress activation routine
+	do_action( 'bbp_new_site', $blog_id, $user_id, $domain, $path, $site_id, $meta );
+
+	// Restore original site
+	bbp_restore_current_site();
+}
+
+/**
+ * Show icons in list-table column headers instead of strings
+ *
+ * @since 2.6.0 bbPress (r5833)
+ *
+ * @param  array $columns Column headers fed into list-table objects
+ *
+ * @return array Possibly altered column headers
+ */
+function bbp_filter_column_headers( $columns = array() ) {
+
+	// Do not filter column headers by default - maybe we'll turn it on later
+	if ( ! apply_filters( 'bbp_filter_column_headers', false ) ) {
+		return $columns;
+	}
+
+	/** Forums ****************************************************************/
+
+	// Forum topic count
+	if ( isset( $columns[ 'bbp_forum_topic_count' ] ) ) {
+		$columns[ 'bbp_forum_topic_count' ] = '<span class="vers bbp_topics_column"  title="' . esc_attr__( 'Topics', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Topics', 'bbpress' ) . '</span></span>';
+	}
+
+	// Forum reply count
+	if ( isset( $columns[ 'bbp_forum_reply_count' ] ) ) {
+		$columns[ 'bbp_forum_reply_count' ] = '<span class="vers bbp_replies_column"  title="' . esc_attr__( 'Replies', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Replies', 'bbpress' ) . '</span></span>';
+	}
+
+	/** Topics ****************************************************************/
+
+	// Topic forum
+	if ( isset( $columns[ 'bbp_topic_forum' ] ) ) {
+		$columns[ 'bbp_topic_forum' ] = '<span class="vers bbp_forums_column"  title="' . esc_attr__( 'Forum', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Forum', 'bbpress' ) . '</span></span>';
+	}
+
+	// Topic reply count
+	if ( isset( $columns[ 'bbp_topic_reply_count' ] ) ) {
+		$columns[ 'bbp_topic_reply_count' ] = '<span class="vers bbp_replies_column"  title="' . esc_attr__( 'Replies', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Replies', 'bbpress' ) . '</span></span>';
+	}
+
+	/** Replies ***************************************************************/
+
+	// Reply forum
+	if ( isset( $columns[ 'bbp_reply_forum' ] ) ) {
+		$columns[ 'bbp_reply_forum' ] = '<span class="vers bbp_forums_column"  title="' . esc_attr__( 'Forum', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Forum', 'bbpress' ) . '</span></span>';
+	}
+
+	// Reply topic
+	if ( isset( $columns[ 'bbp_reply_topic' ] ) ) {
+		$columns[ 'bbp_reply_topic' ] = '<span class="vers bbp_topics_column"  title="' . esc_attr__( 'Topic', 'bbpress' ) . '"><span class="screen-reader-text">' . esc_html__( 'Topic', 'bbpress' ) . '</span></span>';
+	}
+
+	return $columns;
+}
+
+/**
+ * Filter sample permalinks so that certain languages display properly.
+ *
+ * @since 2.0.0 bbPress (r3336)
+ *
+ * @param string $post_link Custom post type permalink
+ * @param object $_post Post data object
+ * @param bool $leavename Optional, defaults to false. Whether to keep post name or page name.
+ * @param bool $sample Optional, defaults to false. Is it a sample permalink.
+ *
+ * @return string The custom post type permalink
+ */
+function bbp_filter_sample_permalink( $post_link, $_post, $leavename = false, $sample = false ) {
+
+	// Bail if not on an admin page and not getting a sample permalink
+	if ( ! empty( $sample ) && is_admin() && bbp_is_custom_post_type() ) {
+		return urldecode( $post_link );
+	}
+
+	// Return post link
+	return $post_link;
+}
+
+/** Sub-Actions ***************************************************************/
+
+/**
+ * Piggy back admin_init action
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_admin_init() {
+	do_action( 'bbp_admin_init' );
+}
+
+/**
+ * Piggy back admin_menu action
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_admin_menu() {
+	do_action( 'bbp_admin_menu' );
+}
+
+/**
+ * Piggy back admin_head action
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_admin_head() {
+	do_action( 'bbp_admin_head' );
+}
+
+/**
+ * Piggy back admin_notices action
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_admin_notices() {
+	do_action( 'bbp_admin_notices' );
+}
+
+/**
+ * Dedicated action to register bbPress importers
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_register_importers() {
+	do_action( 'bbp_register_importers' );
+}
+
+/**
+ * Dedicated action to register admin styles
+ *
+ * @since 2.6.0 bbPress (r6912)
+ */
+function bbp_register_admin_styles() {
+
+	/**
+	 * Action used to register the admin styling
+	 *
+	 * @since 2.1.0
+	 * @deprecated 2.6.0
+	 */
+	do_action( 'bbp_register_admin_style' );
+
+	/**
+	 * Action used to register all admin styling
+	 *
+	 * @since 2.6.0
+	 */
+	do_action( 'bbp_register_admin_styles' );
+}
+
+/**
+ * Dedicated action to register admin scripts
+ *
+ * @since 2.6.0 bbPress (r6912)
+ */
+function bbp_register_admin_scripts() {
+	do_action( 'bbp_register_admin_scripts' );
+}
+
+/**
+ * Dedicated action to register admin settings
+ *
+ * @since 2.1.0 bbPress (r3766)
+ */
+function bbp_register_admin_settings() {
+	do_action( 'bbp_register_admin_settings' );
+}
+
+/**
+ * Dedicated action to output admin tools.php sections
+ *
+ * @since 2.6.0 bbPress (r6273)
+ */
+function bbp_admin_tool_box() {
+	do_action( 'bbp_admin_tool_box' );
+}
+
+/**
+ * Dedicated action to hook into the current screen
+ *
+ * @since 2.6.0 bbPress (r6185)
+ *
+ * @param WP_Screen $current_screen
+ */
+function bbp_current_screen( $current_screen = '' ) {
+	do_action( 'bbp_current_screen', $current_screen );
+}
Index: /tags/2.6.6/src/includes/admin/assets/css/admin.css
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/css/admin.css	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/css/admin.css	(revision 7162)
@@ -0,0 +1,653 @@
+
+/* Kludge for too-wide forums dropdown */
+#poststuff #bbp_forum_attributes select#parent_id,
+#poststuff #bbp_topic_attributes select#parent_id,
+#poststuff #bbp_reply_attributes select#bbp_forum_id,
+#poststuff #bbp_reply_attributes select#bbp_reply_to {
+	max-width: 170px;
+}
+
+/* Version Badge */
+
+.bbp-badge {
+	transform-origin: top left;
+	animation: swoop 16s infinite linear;
+}
+
+.bbp-bee {
+	font: 400 80px/1 dashicons !important;
+	color: #000;
+	position: absolute;
+	height: 60px;
+	width: 60px;
+}
+
+.bbp-hive {
+	font: 400 150px/1 dashicons !important;
+	color: #ccc;
+	position: absolute;
+	height: 150px;
+	width: 150px;
+	top: 0;
+	right: 0;
+	display: block;
+}
+
+.bbp-bee::before {
+	content: "\f451";
+}
+
+.bbp-hive::before {
+	content: "\f449";
+}
+
+.notice-bbpress {
+	border-left: 4px solid #78cd95;
+	padding-left: 36px;
+	background-color: rgb(235, 255, 235);
+	position: relative;
+}
+
+.notice-bbpress .bbpress-logo-icon {
+	position: absolute;
+	height: auto;
+	width: auto;
+	padding: 7px 9px;
+	top: 50%;
+	left: 0;
+	transform: translateY(-50%);
+	font: 400 24px/1 dashicons !important;
+}
+
+.notice-bbpress .bbpress-logo-icon::before {
+	content: "\f477";
+	color: #555;
+}
+
+@keyframes buzz {
+
+	0% {
+		right: -2px;
+		top: -1px;
+	}
+
+	25% {
+		right: 1px;
+		top: 2px;
+	}
+
+	50% {
+		right: -3px;
+		top: -1px;
+	}
+
+	75% {
+		right: 2px;
+		top: 1px;
+	}
+
+	0% {
+		right: -2px;
+		top: -1px;
+	}
+}
+
+@keyframes swoop {
+
+	0% {
+		transform: rotate(95deg);
+	}
+
+	100% {
+		transform: rotate(455deg);
+	}
+}
+
+.about-wrap .bbp-badge {
+	position: absolute;
+	right: 50px;
+	top: 50px;
+	height: 25px;
+	width: 25px;
+}
+
+body.rtl .about-wrap .bbp-badge {
+	right: auto;
+	left: 50px;
+}
+
+/* Dashicons */
+
+th .bbp_forums_column::before,
+#dashboard_right_now a.bbp-glance-forums::before,
+#adminmenu #menu-posts-forum .wp-menu-image::before {
+	content: "\f449";
+}
+
+th .bbp_topics_column::before,
+#dashboard_right_now a.bbp-glance-topics::before,
+#adminmenu #menu-posts-topic .wp-menu-image::before {
+	content: "\f450";
+}
+
+th .bbp_replies_column::before,
+#dashboard_right_now a.bbp-glance-replies::before,
+#adminmenu #menu-posts-reply .wp-menu-image::before {
+	content: "\f451";
+}
+
+#dashboard_right_now a.bbp-glance-topic-tags::before {
+	content: "\f323";
+}
+
+#dashboard_right_now a.bbp-glance-users::before {
+	content: "\f110";
+}
+
+th .bbp_forums_column,
+th .bbp_topics_column,
+th .bbp_replies_column {
+	height: 16px;
+	width: 16px;
+}
+
+th .bbp_forums_column::before,
+th .bbp_topics_column::before,
+th .bbp_replies_column::before {
+	font: 400 20px/0.5 dashicons;
+	speak: none;
+	display: inline-block;
+	padding: 0;
+	top: 4px;
+	left: -4px;
+	position: relative;
+	vertical-align: top;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+	text-decoration: none !important;
+	color: #444;
+}
+
+/* Deprecated Dashboard Metabox */
+
+#bbp-dashboard-right-now p.sub,
+#bbp-dashboard-right-now .table,
+#bbp-dashboard-right-now .versions {
+	margin: -12px;
+}
+
+#bbp-dashboard-right-now .inside {
+	font-size: 12px;
+	padding-top: 20px;
+	margin-bottom: 0;
+}
+
+#bbp-dashboard-right-now p.sub {
+	padding: 5px 0 15px;
+	color: #8f8f8f;
+	font-size: 14px;
+	position: absolute;
+	top: -17px;
+	left: 15px;
+}
+
+body.rtl #bbp-dashboard-right-now p.sub {
+	right: 15px;
+	left: 0;
+}
+
+#bbp-dashboard-right-now .table {
+	margin: 0;
+	padding: 0;
+	position: relative;
+}
+
+#bbp-dashboard-right-now .table_content {
+	float: left;
+	border-top: #ececec 1px solid;
+	width: 45%;
+}
+
+body.rtl #bbp-dashboard-right-now .table_content {
+	float: right;
+}
+
+#bbp-dashboard-right-now .table_discussion {
+	float: right;
+	border-top: #ececec 1px solid;
+	width: 45%;
+}
+
+body.rtl #bbp-dashboard-right-now .table_discussion {
+	float: left;
+}
+
+#bbp-dashboard-right-now table td {
+	padding: 3px 0;
+	white-space: nowrap;
+}
+
+#bbp-dashboard-right-now table tr.first td {
+	border-top: none;
+}
+
+#bbp-dashboard-right-now td.b {
+	padding-right: 6px;
+	text-align: right;
+	font-family: Georgia, "Times New Roman", "Bitstream Charter", Times, serif;
+	font-size: 14px;
+	width: 1%;
+}
+
+body.rtl #bbp-dashboard-right-now td.b {
+	padding-left: 6px;
+	padding-right: 0;
+}
+
+#bbp-dashboard-right-now td.b a {
+	font-size: 18px;
+}
+
+#bbp-dashboard-right-now td.b a:hover {
+	color: #d54e21;
+}
+
+#bbp-dashboard-right-now .t {
+	font-size: 12px;
+	padding-right: 12px;
+	padding-top: 6px;
+	color: #777;
+}
+
+body.rtl #bbp-dashboard-right-now .t {
+	padding-left: 12px;
+	padding-right: 0;
+}
+
+#bbp-dashboard-right-now .t a {
+	white-space: nowrap;
+}
+
+#bbp-dashboard-right-now .spam {
+	color: #f00;
+}
+
+#bbp-dashboard-right-now .waiting {
+	color: #e66f00;
+}
+
+#bbp-dashboard-right-now .approved {
+	color: #0f0;
+}
+
+#bbp-dashboard-right-now .versions {
+	padding: 6px 10px 12px;
+	clear: both;
+}
+
+#bbp-dashboard-right-now .versions .b {
+	font-weight: 700;
+}
+
+#bbp-dashboard-right-now a.button {
+	float: right;
+	clear: right;
+	position: relative;
+	top: -5px;
+}
+
+body.rtl #bbp-dashboard-right-now a.button {
+	float: left;
+	clear: left;
+}
+
+/* List Tables */
+
+body.post-type-forum #minor-publishing,
+body.post-type-forum #save-post {
+	display: none;
+}
+
+body.post-type-forum strong.label,
+body.post-type-topic strong.label,
+body.post-type-reply strong.label {
+	display: inline-block;
+	width: 60px;
+}
+
+.column-bbp_forum_topic_count,
+.column-bbp_forum_reply_count,
+.column-bbp_topic_reply_count,
+.column-bbp_topic_voice_count {
+	width: 8% !important;
+}
+
+.column-author,
+.column-bbp_forum_mods,
+.column-bbp_reply_author,
+.column-bbp_topic_author {
+	width: 10% !important;
+}
+
+.column-bbp_topic_forum,
+.column-bbp_reply_forum,
+.column-bbp_reply_topic {
+	width: 10% !important;
+}
+
+.column-bbp_forum_freshness,
+.column-bbp_topic_freshness {
+	width: 10% !important;
+}
+
+.column-bbp_user_role,
+.column-bbp_forum_created,
+.column-bbp_topic_created,
+.column-bbp_reply_created {
+	width: 15% !important;
+}
+
+.column-bbp_topic_reply_author,
+.column-bbp_forum_topic_author {
+	width: 25% !important;
+}
+
+.column-bbp_topic_reply_author .avatar,
+.column-bbp_forum_topic_author .avatar {
+	float: left;
+	margin-right: 10px;
+}
+
+#bbp_moderators {
+	width: 100%;
+}
+
+#bbp_forum_attributes hr {
+	border-style: solid;
+	border-width: 1px;
+	border-color: #ccc #fff #fff #ccc;
+}
+
+body.post-type-forum #the-list .status-closed,
+body.post-type-topic #the-list .status-closed,
+body.post-type-reply #the-list .status-closed {
+	background-color: #f5f5f5;
+}
+
+body.post-type-forum #the-list .status-closed:nth-child(odd),
+body.post-type-topic #the-list .status-closed:nth-child(odd),
+body.post-type-reply #the-list .status-closed:nth-child(odd) {
+	background-color: #f0f0f0;
+}
+
+body.post-type-forum #the-list .status-spam,
+body.post-type-topic #the-list .status-spam,
+body.post-type-reply #the-list .status-spam,
+#bbp-reply-list .status-trash {
+	background-color: #fee;
+}
+
+body.post-type-forum #the-list .status-spam:nth-child(odd),
+body.post-type-topic #the-list .status-spam:nth-child(odd),
+body.post-type-reply #the-list .status-spam:nth-child(odd),
+#bbp-reply-list .status-trash:nth-child(odd) {
+	background-color: #fdd;
+}
+
+body.post-type-forum #the-list .status-pending,
+body.post-type-topic #the-list .status-pending,
+body.post-type-reply #the-list .status-pending {
+	background-color: #fff2e8;
+}
+
+body.post-type-forum #the-list .status-pending:nth-child(odd),
+body.post-type-topic #the-list .status-pending:nth-child(odd),
+body.post-type-reply #the-list .status-pending:nth-child(odd) {
+	background-color: #fff7f1;
+}
+
+body.post-type-forum #the-list .status-closed td,
+body.post-type-topic #the-list .status-closed td,
+body.post-type-reply #the-list .status-closed td,
+body.post-type-forum #the-list .status-spam td,
+body.post-type-topic #the-list .status-spam td,
+body.post-type-reply #the-list .status-spam td,
+#bbp-reply-list .status-trash td {
+	color: #999;
+}
+
+body.post-type-forum #the-list .row-actions .unspam a,
+body.post-type-topic #the-list .row-actions .unspam a,
+body.post-type-reply #the-list .row-actions .unspam a,
+body.post-type-forum #the-list .row-actions .approved a,
+body.post-type-topic #the-list .row-actions .approved a,
+body.post-type-reply #the-list .row-actions .approved a {
+	color: #006505;
+}
+
+/* User Relationships */
+
+#bbp_topic_engagements_metabox .avatar,
+#bbp_topic_favorites_metabox .avatar,
+#bbp_topic_subscriptions_metabox .avatar {
+	margin: 3px;
+}
+
+/* Converter */
+
+.bbp-converter-settings-wrap {
+	float: left;
+	width: 55%;
+}
+
+#poststuff.bbp-converter-monitor-wrap {
+	float: right;
+	width: 43%;
+	margin-left: 2%;
+	min-width: 200px;
+}
+
+#bbp-converter-monitor h2 {
+	position: relative;
+}
+
+#bbp-converter-monitor .inside {
+	margin: 0;
+	padding: 5px;
+	border-bottom: 1px solid #e5e5e5;
+}
+
+#bbp-converter-monitor div.actions {
+	padding: 10px;
+	background: #fafafa;
+	clear: both;
+}
+
+#bbp-converter-spinner {
+	margin: 4px 0;
+}
+
+div.bbp-converter-log,
+div.bbp-converter-warning {
+	padding: 5px 0 5px 5px;
+}
+
+div.bbp-converter-log.started {
+	height: 300px;
+	overflow: auto;
+}
+
+div.bbp-converter-log p {
+	margin: 0;
+	padding: 2px;
+}
+
+div.bbp-converter-log p:only-child {
+	float: none;
+	margin-bottom: 0;
+}
+
+div.bbp-converter-log div {
+	padding: 10px;
+	margin: 5px;
+	background: #f4f4f4;
+	border: 1px solid #ddd;
+}
+
+div.bbp-converter-log div code {
+	margin-top: 5px;
+	display: block;
+}
+
+div.bbp-converter-log .step {
+	text-align: right;
+	font-weight: 600;
+}
+
+div.bbp-converter-log .output {
+	margin: 0 5px;
+}
+
+div.bbp-converter-log .mini-step {
+	padding: 3px 5px;
+	vertical-align: middle;
+	font-size: 8px;
+	font-weight: 600;
+	border-radius: 6px;
+	background-color: #aaa;
+	color: #fff;
+}
+
+#bbp-converter-monitor .bbp-progress-bar {
+	position: absolute;
+	left: 0;
+	height: 1px;
+	width: 0;
+	background-color: #00b9eb;
+	transition-property: width;
+	transition-timing-function: ease-out;
+	transition-duration: 1s;
+}
+
+#bbp-converter-monitor #bbp-converter-step-percentage {
+	bottom: 1px;
+}
+
+#bbp-converter-monitor #bbp-converter-total-percentage {
+	bottom: 0;
+}
+
+#bbp-converter-stop {
+	display: none;
+}
+
+#bbp-converter-status {
+	font-weight: 400;
+	font-size: 12px;
+	color: #aaa;
+	display: block;
+	margin-top: 2px;
+}
+
+.bbp-converter-db-password-wrapper {
+	display: inline-block;
+	position: relative;
+}
+
+.bbp-converter-db-pass::-webkit-credentials-auto-fill-button {
+	display: none;
+	visibility: hidden;
+	pointer-events: none;
+	position: absolute;
+	right: 0;
+}
+
+button.bbp-db-pass-toggle {
+	border: none;
+	background: transparent;
+	position: absolute;
+	right: 3px;
+	top: 3px;
+	padding: 0;
+	margin: 0;
+	cursor: pointer;
+	color: #aaa;
+	height: 25px;
+	width: 25px;
+	font: 400 20px/1 dashicons !important;
+}
+
+button.bbp-db-pass-toggle:hover {
+	color: #888;
+}
+
+button.bbp-db-pass-toggle.text::after {
+	content: "\f530";
+}
+
+button.bbp-db-pass-toggle.password::after {
+	content: "\f177";
+}
+
+@media screen and ( max-width: 782px ) {
+
+	.bbp-converter-db-password-wrapper {
+		width: 100%;
+	}
+
+	#poststuff.bbp-converter-monitor-wrap,
+	.bbp-converter-settings-wrap {
+		float: none;
+		width: 100%;
+		margin: 20px 0;
+	}
+
+	button.bbp-db-pass-toggle {
+		height: 35px;
+		width: 35px;
+		font: 400 20px/1 dashicons !important;
+	}
+}
+
+/* Tools */
+
+td.bbp-tool-title strong {
+	display: block;
+}
+
+td.bbp-tool-title p {
+	margin: 0;
+}
+
+.manage-column.column-components {
+	width: 20%;
+}
+
+.manage-column.column-version {
+	width: 11%;
+}
+
+.manage-column.column-overhead {
+	width: 13%;
+}
+
+.nav-tab-wrapper a span.awaiting-mod {
+	display: inline-block;
+	vertical-align: top;
+	margin: 3px 0 0 1px;
+	padding: 0 5px;
+	min-width: 7px;
+	height: 17px;
+	border-radius: 11px;
+	background-color: #ca4a1f;
+	color: #fff;
+	font-size: 9px;
+	line-height: 17px;
+	text-align: center;
+	z-index: 26;
+}
+
+/* Retro TV corners for contributor avatars */
+.dashboard_page_bbp-credits .wp-person img {
+	border-radius: 20px;
+}
Index: /tags/2.6.6/src/includes/admin/assets/js/badge.js
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/js/badge.js	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/js/badge.js	(revision 7162)
@@ -0,0 +1,46 @@
+(function () {
+	window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
+
+	var field    = document.getElementById( 'bbp-badge' ),
+		bee      = document.getElementById( 'bbp-bee'   ),
+
+		max_x    = field.clientWidth  - bee.offsetWidth,
+		max_y    = field.clientHeight - bee.offsetHeight,
+		angle    = 95,
+		offset   = 95,
+
+		duration = 4,
+		canvas   = 50,
+		start    = null,
+		variance = 1;
+
+	function step( timestamp ) {
+		var progress, x, y;
+
+		if ( start === null ) {
+			start    = timestamp;
+			variance = 1;
+			angle    = 95;
+		}
+
+		progress = ( timestamp - start ) / duration / 1000;
+		angle    = ( 360 * progress ) + offset;
+
+		x = variance * Math.sin( progress * 2 * Math.PI );
+		y = Math.cos( progress * 2 * Math.PI );
+
+		bee.style.left            = max_x / 2 + ( canvas * x ) + 'px';
+		bee.style.bottom          = max_y / 2 + ( canvas * y ) + 'px';
+		bee.style.transform       = 'rotate(' + angle + 'deg)';
+		bee.style.webkitTransform = 'rotate(' + angle + 'deg)';
+
+		// Reset
+		if ( progress >= 1 ) {
+			start = null;
+		}
+
+		requestAnimationFrame( step );
+	}
+
+	requestAnimationFrame( step );
+})();
Index: /tags/2.6.6/src/includes/admin/assets/js/common.js
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/js/common.js	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/js/common.js	(revision 7162)
@@ -0,0 +1,15 @@
+jQuery( document ).ready( function() {
+
+	var bbp_author_id = jQuery( '#bbp_author_id' );
+
+	bbp_author_id.suggest(
+		bbp_author_id.data( 'ajax-url' ),
+		{
+			minchars: 1, // Allow single-digit user IDs
+			onSelect: function() {
+				var value = this.value;
+				bbp_author_id.val( value.substr( 0, value.indexOf( ' ' ) ) );
+			}
+		}
+	);
+} );
Index: /tags/2.6.6/src/includes/admin/assets/js/converter.js
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/js/converter.js	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/js/converter.js	(revision 7162)
@@ -0,0 +1,344 @@
+/*jshint sub:true*/
+/* global document, jQuery, ajaxurl, BBP_Converter */
+jQuery( document ).ready( function ( $ ) {
+	'use strict';
+
+	// Variables
+	var message  = $( '#bbp-converter-message'  ),
+		stop     = $( '#bbp-converter-stop'     ),
+		start    = $( '#bbp-converter-start'    ),
+		restart  = $( '#_bbp_converter_restart' ),
+		status   = $( '#bbp-converter-status'   ),
+		spinner  = $( '#bbp-converter-spinner'  ),
+		settings = $( '#bbp-converter-settings' ),
+		password = $( '#_bbp_converter_db_pass' ),
+		toggle   = $( '.bbp-db-pass-toggle'     ),
+		step_p   = $( '#bbp-converter-step-percentage'  ),
+		total_p  = $( '#bbp-converter-total-percentage' ),
+		fields   = settings.find( 'table:first-of-type input, table:first-of-type select' );
+
+	/**
+	 * Show/hide db password button toggle
+	 *
+	 * @since 2.6.0 bbPress (r6676)
+	 *
+	 * @param {element} e
+	 */
+	toggle.on( 'click', function( e ) {
+		var type = ( password.attr( 'type' ) === 'password' ) ? 'text' : 'password';
+
+		password.attr( 'type', type );
+
+		toggle
+			.toggleClass( 'password' )
+			.toggleClass( 'text' );
+
+		e.preventDefault();
+	});
+
+	/**
+	 * Start button click
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @param {element} e
+	 */
+	start.on( 'click', function( e ) {
+		bbp_converter_user_start();
+		e.preventDefault();
+	} );
+
+	/**
+	 * Stop button click
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @param {element} e
+	 */
+	$( stop ).on( 'click', function( e ) {
+		bbp_converter_user_stop();
+		e.preventDefault();
+	} );
+
+	/**
+	 * Start the converter
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_user_start() {
+		bbp_converter_start();
+	}
+
+	/**
+	 * Stop the converter
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_user_stop() {
+		bbp_converter_stop(
+			BBP_Converter.strings.button_continue,
+			BBP_Converter.strings.import_stopped_user
+		);
+	}
+
+	/**
+	 * Return values of converter settings
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @returns {converterL#2.bbp_converter_settings.values}
+	 */
+	function bbp_converter_settings() {
+		var values = {};
+
+		$.each( settings.serializeArray(), function( i, field ) {
+			values[ field.name ] = field.value;
+		} );
+
+		if ( values['_bbp_converter_restart'] ) {
+			restart.removeAttr( 'checked' );
+		}
+
+		if ( values['_bbp_converter_delay_time'] ) {
+			BBP_Converter.state.delay = parseInt( values['_bbp_converter_delay_time'], 10 ) * 1000;
+		}
+
+		values['action']      = 'bbp_converter_process';
+		values['_ajax_nonce'] = BBP_Converter.ajax_nonce;
+
+		return values;
+	}
+
+	/**
+	 * Run the converter step
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_post() {
+		$.post( ajaxurl, bbp_converter_settings(), function( response ) {
+
+			// Parse the json response
+			try {
+				var data = response.data;
+
+				// Success
+				if ( true === response.success ) {
+					bbp_converter_step( data );
+
+				// Failure
+				} else {
+					bbp_converter_stop();
+				}
+
+			} catch( e ) {
+				bbp_converter_stop();
+			}
+		}, 'json' );
+	}
+
+	/**
+	 * Process the next step
+	 *
+	 * @since 2.6.0 bbPress (r6600)
+	 *
+	 * @param {object} data
+	 * @returns {void}
+	 */
+	function bbp_converter_step( data ) {
+
+		// Bail if not running
+		if ( ! BBP_Converter.state.running ) {
+			return;
+		}
+
+		// Do the step
+		bbp_converter_log( data.progress );
+		bbp_converter_percentages( data.step_percent, data.total_percent );
+		bbp_converter_status( data );
+		bbp_converter_wait();
+
+		// Done
+		if ( data.current_step === data.final_step ) {
+			bbp_converter_stop(
+				BBP_Converter.strings.button_start,
+				BBP_Converter.strings.import_complete
+			);
+		}
+	}
+
+	/**
+	 * Wait to do the next AJAX request
+	 *
+	 * @since 2.6.0 bbPress (r6600)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_wait() {
+		clearTimeout( BBP_Converter.state.running );
+
+		// Bail if not running
+		if ( ! BBP_Converter.state.running ) {
+			return;
+		}
+
+		// Wait, then POST
+		BBP_Converter.state.running = setTimeout( function() {
+			bbp_converter_post();
+		}, parseInt( BBP_Converter.state.delay, 10 ) );
+	}
+
+	/**
+	 * Start the converter and set the various flags
+	 *
+	 * @since 2.6.0 bbPress (r6600)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_start() {
+		clearTimeout( BBP_Converter.state.running );
+		clearInterval( BBP_Converter.state.status );
+
+		BBP_Converter.state.running = true;
+
+		var log = BBP_Converter.strings.start_continue;
+		if ( false === BBP_Converter.state.started ) {
+			log = BBP_Converter.strings.start_start;
+			BBP_Converter.state.started = true;
+		}
+
+		bbp_converter_update(
+			BBP_Converter.strings.button_continue,
+			log,
+			BBP_Converter.strings.status_starting
+		);
+
+		message.addClass( 'started' );
+
+		start.hide();
+		stop.show();
+console.log( fields );
+		spinner.css( 'visibility', 'visible' );
+		fields.prop( 'readonly', true );
+
+		bbp_converter_post();
+	}
+
+	/**
+	 * Stop the converter, and update the UI
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @param {string} button New text for button
+	 * @param {string} log    New text to add to import monitor
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_stop( button, log ) {
+		clearTimeout( BBP_Converter.state.running );
+		clearInterval( BBP_Converter.state.status );
+
+		BBP_Converter.state.running = false;
+		BBP_Converter.state.status  = false;
+
+		if ( ! button ) {
+			button = BBP_Converter.strings.button_continue;
+		}
+
+		if ( ! log ) {
+			log = BBP_Converter.strings.status_stopped;
+		}
+
+		bbp_converter_update(
+			button,
+			log,
+			BBP_Converter.strings.status_stopped
+		);
+
+		start.show();
+		stop.hide();
+
+		spinner.css( 'visibility', 'hidden' );
+		fields.prop( 'readonly', false );
+	}
+
+	/**
+	 * Update the various screen texts
+	 *
+	 * @since 2.6.0 bbPress (r6600)
+	 *
+	 * @param {string} b_text
+	 * @param {string} p_text
+	 * @param {string} s_text
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_update( b_text, p_text, s_text ) {
+		start.val( b_text );
+		bbp_converter_log( p_text );
+		status.text( s_text );
+	}
+
+	/**
+	 * Update the status
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 *
+	 * @param {object} data
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_status( data ) {
+		var remaining = parseInt( BBP_Converter.state.delay, 10 ) / 1000;
+
+		status.text( BBP_Converter.strings.status_counting.replace( '%s', remaining ) );
+		clearInterval( BBP_Converter.state.status );
+
+		BBP_Converter.state.status = setInterval( function() {
+			remaining--;
+			status.text( BBP_Converter.strings.status_counting.replace( '%s', remaining ) );
+
+			if ( remaining <= 0 ) {
+				clearInterval( BBP_Converter.state.status );
+
+				if ( parseInt( data.current_step, 10 ) < parseInt( data.final_step, 10 ) ) {
+					status.text( BBP_Converter.strings.status_up_next.replace( '%s', data.current_step ) );
+				} else {
+					status.text( BBP_Converter.strings.status_complete );
+				}
+			}
+		}, 1000 );
+	}
+
+	/**
+	 * Prepend some text to the import monitor
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @param {string} text Text to prepend to the import monitor
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_log( text ) {
+		text = '<p>' + text + '</p>';
+
+		message.prepend( text );
+	}
+
+	/**
+	 * Prepend some text to the import monitor
+	 *
+	 * @since 2.6.0 bbPress (r6470)
+	 *
+	 * @returns {void}
+	 */
+	function bbp_converter_percentages( step_percent, total_percent ) {
+		step_p.width( step_percent + '%' );
+		total_p.width( total_percent + '%' );
+	}
+} );
Index: /tags/2.6.6/src/includes/admin/assets/js/replies.js
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/js/replies.js	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/js/replies.js	(revision 7162)
@@ -0,0 +1,17 @@
+jQuery( document ).ready(function() {
+
+	jQuery( '#misc-publishing-actions' ).find( '.misc-pub-section' ).first().remove();
+	jQuery( '#save-action' ).remove();
+
+	var bbp_topic_id = jQuery( '#bbp_topic_id' );
+
+	bbp_topic_id.suggest(
+		bbp_topic_id.data( 'ajax-url' ),
+		{
+			onSelect: function() {
+				var value = this.value;
+				bbp_topic_id.val( value.substr( 0, value.indexOf( ' ' ) ) );
+			}
+		}
+	);
+} );
Index: /tags/2.6.6/src/includes/admin/assets/js/topics.js
===================================================================
--- /tags/2.6.6/src/includes/admin/assets/js/topics.js	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/assets/js/topics.js	(revision 7162)
@@ -0,0 +1,4 @@
+jQuery( document ).ready( function() {
+	jQuery( '#misc-publishing-actions' ).find( '.misc-pub-section' ).first().remove();
+	jQuery( '#save-action' ).remove();
+} );
Index: /tags/2.6.6/src/includes/admin/classes/class-bbp-admin.php
===================================================================
--- /tags/2.6.6/src/includes/admin/classes/class-bbp-admin.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/classes/class-bbp-admin.php	(revision 7162)
@@ -0,0 +1,1426 @@
+<?php
+
+/**
+ * Main bbPress Admin Class
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Admin' ) ) :
+/**
+ * Loads bbPress plugin admin area
+ *
+ * @package bbPress
+ * @subpackage Administration
+ * @since 2.0.0 bbPress (r2464)
+ */
+class BBP_Admin {
+
+	/** Directory *************************************************************/
+
+	/**
+	 * @var string Path to the bbPress admin directory
+	 */
+	public $admin_dir = '';
+
+	/** URLs ******************************************************************/
+
+	/**
+	 * @var string URL to the bbPress admin directory
+	 */
+	public $admin_url = '';
+
+	/**
+	 * @var string URL to the bbPress images directory
+	 */
+	public $images_url = '';
+
+	/**
+	 * @var string URL to the bbPress admin styles directory
+	 */
+	public $styles_url = '';
+
+	/**
+	 * @var string URL to the bbPress admin css directory
+	 */
+	public $css_url = '';
+
+	/**
+	 * @var string URL to the bbPress admin js directory
+	 */
+	public $js_url = '';
+
+	/** Capability ************************************************************/
+
+	/**
+	 * @var bool Minimum capability to access Tools and Settings
+	 */
+	public $minimum_capability = 'keep_gate';
+
+	/** Separator *************************************************************/
+
+	/**
+	 * @var bool Whether or not to add an extra top level menu separator
+	 */
+	public $show_separator = false;
+
+	/** Tools *****************************************************************/
+
+	/**
+	 * @var array Array of available repair tools
+	 */
+	public $tools = array();
+
+	/** Notices ***************************************************************/
+
+	/**
+	 * @var array Array of notices to output to the current user
+	 */
+	public $notices = array();
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main bbPress admin loader
+	 *
+	 * @since 2.0.0 bbPress (r2515)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->includes();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Admin globals
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+		$bbp              = bbpress();
+		$this->admin_dir  = trailingslashit( $bbp->includes_dir . 'admin'      ); // Admin path
+		$this->admin_url  = trailingslashit( $bbp->includes_url . 'admin'      ); // Admin url
+
+		// Assets
+		$this->css_url    = trailingslashit( $this->admin_url   . 'assets/css' ); // Admin css URL
+		$this->js_url     = trailingslashit( $this->admin_url   . 'assets/js'  ); // Admin js URL
+		$this->styles_url = trailingslashit( $this->admin_url   . 'styles'     ); // Admin styles URL
+
+		// Deprecated
+		$this->images_url = trailingslashit( $this->admin_url   . 'images'     ); // Admin images URL
+	}
+
+	/**
+	 * Include required files
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function includes() {
+
+		// Tools
+		require $this->admin_dir . 'tools.php';
+		require $this->admin_dir . 'tools/common.php';
+		require $this->admin_dir . 'tools/converter.php';
+		require $this->admin_dir . 'tools/repair.php';
+		require $this->admin_dir . 'tools/upgrade.php';
+		require $this->admin_dir . 'tools/reset.php';
+		require $this->admin_dir . 'tools/help.php';
+
+		// Components
+		require $this->admin_dir . 'settings.php';
+		require $this->admin_dir . 'common.php';
+		require $this->admin_dir . 'metaboxes.php';
+		require $this->admin_dir . 'forums.php';
+		require $this->admin_dir . 'topics.php';
+		require $this->admin_dir . 'replies.php';
+		require $this->admin_dir . 'users.php';
+	}
+
+	/**
+	 * Setup the admin hooks, actions and filters
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Bail to prevent interfering with the deactivation process
+		if ( bbp_is_deactivation() ) {
+			return;
+		}
+
+		/** General Actions ***************************************************/
+
+		add_action( 'bbp_admin_menu',              array( $this, 'admin_menus'             ) );
+		add_action( 'bbp_admin_head',              array( $this, 'admin_head'              ) );
+		add_action( 'bbp_register_admin_styles',   array( $this, 'register_admin_styles'   ) );
+		add_action( 'bbp_register_admin_scripts',  array( $this, 'register_admin_scripts'  ) );
+		add_action( 'bbp_register_admin_settings', array( $this, 'register_admin_settings' ) );
+
+		// Enqueue styles & scripts
+		add_action( 'admin_enqueue_scripts',       array( $this, 'enqueue_styles'  ) );
+		add_action( 'admin_enqueue_scripts',       array( $this, 'enqueue_scripts' ) );
+
+		/** Notices ***********************************************************/
+
+		add_action( 'bbp_admin_init',    array( $this, 'setup_notices'  ) );
+		add_action( 'bbp_admin_init',    array( $this, 'hide_notices'   ) );
+		add_action( 'bbp_admin_notices', array( $this, 'output_notices' ) );
+
+		/** Upgrades **********************************************************/
+
+		add_action( 'bbp_admin_init', array( $this, 'add_upgrade_count' ) );
+
+		/** Ajax **************************************************************/
+
+		// No _nopriv_ equivalent - users must be logged in
+		add_action( 'wp_ajax_bbp_suggest_topic', array( $this, 'suggest_topic' ) );
+		add_action( 'wp_ajax_bbp_suggest_user',  array( $this, 'suggest_user'  ) );
+
+		/** Filters ***********************************************************/
+
+		// Modify admin links
+		add_filter( 'plugin_action_links', array( $this, 'modify_plugin_action_links' ), 10, 2 );
+
+		// Map settings capabilities
+		add_filter( 'bbp_map_meta_caps',   array( $this, 'map_settings_meta_caps' ), 10, 4 );
+
+		// Allow keymasters to save forums settings
+		add_filter( 'option_page_capability_bbpress',  array( $this, 'option_page_capability_bbpress' ) );
+
+		/** Network Admin *****************************************************/
+
+		// Add menu item to settings menu
+		add_action( 'network_admin_menu',  array( $this, 'network_admin_menus' ) );
+
+		/** Dependencies ******************************************************/
+
+		// Allow plugins to modify these actions
+		do_action_ref_array( 'bbp_admin_loaded', array( &$this ) );
+	}
+
+	/**
+	 * Setup general admin area notices.
+	 *
+	 * @since 2.6.0 bbPress (r6701)
+	 */
+	public function setup_notices() {
+
+		// Avoid malformed notices variable
+		if ( ! is_array( $this->notices ) ) {
+			$this->notices = array();
+		}
+
+		// Get page
+		$page = ! empty( $_GET['page'] )
+			? sanitize_key( $_GET['page'] )
+			: false;
+
+		// Pending database upgrades!
+		if ( ( 'bbp-upgrade' !== $page ) && bbp_get_pending_upgrades() && current_user_can( 'bbp_tools_upgrade_page' ) ) {
+
+			// Link to upgrade page
+			$upgrade_url  = add_query_arg( array( 'page' => 'bbp-upgrade', 'status' => 'pending' ), admin_url( 'tools.php' ) );
+			$dismiss_url  = wp_nonce_url( add_query_arg( array( 'bbp-hide-notice' => 'bbp-skip-upgrades' ) ), 'bbp-hide-notice' );
+			$upgrade_link = '<a href="' . esc_url( $upgrade_url ) . '">' . esc_html__( 'Learn More',   'bbpress' ) . '</a>';
+			$dismiss_link = '<a href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Hide For Now', 'bbpress' ) . '</a>';
+			$bbp_dashicon = '<span class="bbpress-logo-icon"></span>';
+			$message      = $bbp_dashicon . sprintf(
+				esc_html__( 'bbPress requires a manual database upgrade. %s or %s.', 'bbpress' ),
+				$upgrade_link,
+				$dismiss_link
+			);
+
+			// Add tools feedback
+			$this->add_notice( $message, 'notice-bbpress', false );
+		}
+	}
+
+	/**
+	 * Handle hiding of general admin area notices.
+	 *
+	 * @since 2.6.0 bbPress (r6701)
+	 */
+	public function hide_notices() {
+
+		// Hiding a notice?
+		$hiding_notice = ! empty( $_GET['bbp-hide-notice'] )
+			? sanitize_key( $_GET['bbp-hide-notice'] )
+			: false;
+
+		// Bail if not hiding a notice
+		if ( empty( $hiding_notice ) ) {
+			return;
+		}
+
+		// Bail if user cannot visit upgrade page (cannot clear notice either!)
+		if ( ! current_user_can( 'bbp_tools_upgrade_page' ) ) {
+			return;
+		}
+
+		// Check the admin referer
+		check_admin_referer( 'bbp-hide-notice' );
+
+		// Maybe delete notices
+		switch ( $hiding_notice ) {
+
+			// Skipped upgrade notice
+			case 'bbp-skip-upgrades' :
+				bbp_clear_pending_upgrades();
+				break;
+		}
+	}
+
+	/**
+	 * Output all admin area notices
+	 *
+	 * @since 2.6.0 bbPress (r6771)
+	 */
+	public function output_notices() {
+
+		// Bail if no notices
+		if ( empty( $this->notices ) || ! is_array( $this->notices ) ) {
+			return;
+		}
+
+		// Start an output buffer
+		ob_start();
+
+		// Loop through notices, and add them to buffer
+		foreach ( $this->notices as $notice ) {
+			echo $notice;
+		}
+
+		// Output the current buffer
+		echo ob_get_clean();
+	}
+
+	/**
+	 * Add a notice to the notices array
+	 *
+	 * @since 2.6.0 bbPress (r6771)
+	 *
+	 * @param string|WP_Error $message        A message to be displayed or {@link WP_Error}
+	 * @param string          $class          Optional. A class to be added to the message div
+	 * @param bool            $is_dismissible Optional. True to dismiss, false to persist
+	 *
+	 * @return void
+	 */
+	public function add_notice( $message, $class = false, $is_dismissible = true ) {
+
+		// One message as string
+		if ( is_string( $message ) ) {
+			$message       = '<p>' . $this->esc_notice( $message ) . '</p>';
+			$default_class ='updated';
+
+		// Messages as objects
+		} elseif ( is_wp_error( $message ) ) {
+			$errors  = $message->get_error_messages();
+
+			switch ( count( $errors ) ) {
+				case 0:
+					return false;
+
+				case 1:
+					$message = '<p>' . $this->esc_notice( $errors[0] ) . '</p>';
+					break;
+
+				default:
+					$escaped = array_map( array( $this, 'esc_notice' ), $errors );
+					$message = '<ul>' . "\n\t" . '<li>' . implode( '</li>' . "\n\t" . '<li>', $escaped ) . '</li>' . "\n" . '</ul>';
+					break;
+			}
+
+			$default_class = 'is-error';
+
+		// Message is an unknown format, so bail
+		} else {
+			return false;
+		}
+
+		// CSS Classes
+		$classes = ! empty( $class )
+			? array( $class )
+			: array( $default_class );
+
+		// Add dismissible class
+		if ( ! empty( $is_dismissible ) ) {
+			array_push( $classes, 'is-dismissible' );
+		}
+
+		// Assemble the message
+		$message = '<div id="message" class="notice ' . implode( ' ', array_map( 'sanitize_html_class', $classes ) ) . '">' . $message . '</div>';
+		$message = str_replace( "'", "\'", $message );
+
+		// Avoid malformed notices variable
+		if ( ! is_array( $this->notices ) ) {
+			$this->notices = array();
+		}
+
+		// Add notice to notices array
+		$this->notices[] = $message;
+	}
+
+	/**
+	 * Escape message string output
+	 *
+	 * @since 2.6.0 bbPress (r6775)
+	 *
+	 * @param string $message
+	 *
+	 * @return string
+	 */
+	private function esc_notice( $message = '' ) {
+
+		// Get allowed HTML
+		$tags = wp_kses_allowed_html();
+
+		// Allow spans with classes in notices
+		$tags['span'] = array(
+			'class' => 1
+		);
+
+		// Parse the message and remove unsafe tags
+		$text = wp_kses( $message, $tags );
+
+		// Return the message text
+		return $text;
+	}
+
+	/**
+	 * Maybe append the pending upgrade count to the "Tools" menu.
+	 *
+	 * @since 2.6.0 bbPress (r6896)
+	 *
+	 * @global menu $menu
+	 */
+	public function add_upgrade_count() {
+		global $menu;
+
+		// Skip if no menu (AJAX, shortinit, etc...)
+		if ( empty( $menu ) ) {
+			return;
+		}
+
+		// Loop through menus, and maybe add the upgrade count
+		foreach ( $menu as $menu_index => $menu_item ) {
+			$found = array_search( 'tools.php', $menu_item, true );
+
+			if ( false !== $found ) {
+				$menu[ $menu_index ][ 0 ] = bbp_maybe_append_pending_upgrade_count( $menu[ $menu_index ][ 0 ] );
+				continue;
+			}
+		}
+	}
+
+	/**
+	 * Add the admin menus
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 */
+	public function admin_menus() {
+
+		// Default hooks array
+		$hooks = array();
+
+		// Get the tools pages
+		$tools = bbp_get_tools_admin_pages();
+
+		// Loop through tools and check
+		if ( ! empty( $tools ) ) {
+			foreach ( $tools as $tool ) {
+
+				// Try to add the admin page
+				$page = add_management_page(
+					$tool['name'],
+					$tool['name'],
+					$tool['cap'],
+					$tool['page'],
+					$tool['func']
+				);
+
+				// Add page to hook if user can view it
+				if ( false !== $page ) {
+					$hooks[] = $page;
+				}
+			}
+
+			// Fudge the highlighted subnav item when on a bbPress admin page
+			if ( ! empty( $hooks ) ) {
+				foreach ( $hooks as $hook ) {
+					add_action( "admin_head-{$hook}", 'bbp_tools_modify_menu_highlight' );
+				}
+			}
+		}
+
+		// Forums Tools Root
+		add_management_page(
+			esc_html__( 'Forums', 'bbpress' ),
+			bbp_maybe_append_pending_upgrade_count( esc_html__( 'Forums', 'bbpress' ) ),
+			'bbp_tools_page',
+			'bbp-repair',
+			'bbp_admin_repair_page'
+		);
+
+		// Are settings enabled?
+		if ( 'basic' === bbp_settings_integration() ) {
+			add_options_page(
+				esc_html__( 'Forums',  'bbpress' ),
+				esc_html__( 'Forums',  'bbpress' ),
+				'bbp_settings_page',
+				'bbpress',
+				'bbp_admin_settings'
+			);
+		}
+
+		// These are later removed in admin_head
+		if ( current_user_can( 'bbp_about_page' ) ) {
+
+			// About
+			add_dashboard_page(
+				esc_html__( 'Welcome to bbPress',  'bbpress' ),
+				esc_html__( 'Welcome to bbPress',  'bbpress' ),
+				'bbp_about_page',
+				'bbp-about',
+				array( $this, 'about_screen' )
+			);
+
+			// Credits
+			add_dashboard_page(
+				esc_html__( 'Welcome to bbPress',  'bbpress' ),
+				esc_html__( 'Welcome to bbPress',  'bbpress' ),
+				'bbp_about_page',
+				'bbp-credits',
+				array( $this, 'credits_screen' )
+			);
+		}
+
+		// Bail if plugin is not network activated
+		if ( ! is_plugin_active_for_network( bbpress()->basename ) ) {
+			return;
+		}
+
+		add_submenu_page(
+			'index.php',
+			esc_html__( 'Update Forums', 'bbpress' ),
+			esc_html__( 'Update Forums', 'bbpress' ),
+			'manage_network',
+			'bbp-update',
+			array( $this, 'update_screen' )
+		);
+	}
+
+	/**
+	 * Add the network admin menus
+	 *
+	 * @since 2.1.0 bbPress (r3689)
+	 */
+	public function network_admin_menus() {
+
+		// Bail if plugin is not network activated
+		if ( ! is_plugin_active_for_network( bbpress()->basename ) ) {
+			return;
+		}
+
+		add_submenu_page(
+			'upgrade.php',
+			esc_html__( 'Update Forums', 'bbpress' ),
+			esc_html__( 'Update Forums', 'bbpress' ),
+			'manage_network',
+			'bbpress-update',
+			array( $this, 'network_update_screen' )
+		);
+	}
+
+	/**
+	 * Register the settings
+	 *
+	 * @since 2.0.0 bbPress (r2737)
+	 *
+	 * @todo Put fields into multidimensional array
+	 */
+	public static function register_admin_settings() {
+
+		// Bail if no sections available
+		$sections = bbp_admin_get_settings_sections();
+		if ( empty( $sections ) ) {
+			return false;
+		}
+
+		// Are we using settings integration?
+		$settings_integration = bbp_settings_integration();
+
+		// Loop through sections
+		foreach ( (array) $sections as $section_id => $section ) {
+
+			// Only proceed if current user can see this section
+			if ( ! current_user_can( $section_id ) ) {
+				continue;
+			}
+
+			// Only add section and fields if section has fields
+			$fields = bbp_admin_get_settings_fields_for_section( $section_id );
+			if ( empty( $fields ) ) {
+				continue;
+			}
+
+			// Overload the converter page
+			if ( ! empty( $section['page'] ) && ( ( 'converter' === $section['page'] ) || ( 'deep' === $settings_integration ) ) ) {
+				$page = $section['page'];
+			} else {
+				$page = 'bbpress';
+			}
+
+			// Add the section
+			add_settings_section( $section_id, $section['title'], $section['callback'], $page );
+
+			// Loop through fields for this section
+			foreach ( (array) $fields as $field_id => $field ) {
+
+				// Skip field if user is not capable
+				if ( ! empty( $field['capability'] ) && ! current_user_can( $field['capability'] ) ) {
+					continue;
+				}
+
+				// Add the field
+				if ( ! empty( $field['callback'] ) && ! empty( $field['title'] ) ) {
+					add_settings_field( $field_id, $field['title'], $field['callback'], $page, $section_id, $field['args'] );
+				}
+
+				// Register the setting
+				register_setting( $page, $field_id, $field['sanitize_callback'] );
+			}
+		}
+	}
+
+	/**
+	 * Maps settings capabilities
+	 *
+	 * @since 2.2.0 bbPress (r4242)
+	 *
+	 * @param array $caps Capabilities for meta capability
+	 * @param string $cap Capability name
+	 * @param int $user_id User id
+	 * @param array $args Arguments
+	 *
+	 * @return array Actual capabilities for meta capability
+	 */
+	public static function map_settings_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+		// What capability is being checked?
+		switch ( $cap ) {
+
+			// Pages
+			case 'bbp_about_page'            : // About and Credits
+			case 'bbp_tools_page'            : // Tools Page
+			case 'bbp_tools_repair_page'     : // Tools - Repair Page
+			case 'bbp_tools_upgrade_page'    : // Tools - Upgrade Page
+			case 'bbp_tools_import_page'     : // Tools - Import Page
+			case 'bbp_tools_reset_page'      : // Tools - Reset Page
+			case 'bbp_settings_page'         : // Settings Page
+
+			// Converter Sections
+			case 'bbp_converter_connection'  : // Converter - Connection
+			case 'bbp_converter_options'     : // Converter - Options
+
+			// Settings Sections
+			case 'bbp_settings_users'        : // Settings - Users
+			case 'bbp_settings_features'     : // Settings - Features
+			case 'bbp_settings_theme_compat' : // Settings - Theme compat
+			case 'bbp_settings_root_slugs'   : // Settings - Root slugs
+			case 'bbp_settings_single_slugs' : // Settings - Single slugs
+			case 'bbp_settings_user_slugs'   : // Settings - User slugs
+			case 'bbp_settings_per_page'     : // Settings - Per page
+			case 'bbp_settings_per_rss_page' : // Settings - Per RSS page
+				$caps = array( bbp_admin()->minimum_capability );
+				break;
+
+			// Extend - BuddyPress
+			case 'bbp_settings_buddypress' :
+				if ( ( is_plugin_active( 'buddypress/bp-loader.php' ) && defined( 'BP_VERSION' ) && bp_is_root_blog() ) && is_super_admin() ) {
+					$caps = array( bbp_admin()->minimum_capability );
+				} else {
+					$caps = array( 'do_not_allow' );
+				}
+
+				break;
+
+			// Extend - Akismet
+			case 'bbp_settings_akismet' :
+				if ( ( is_plugin_active( 'akismet/akismet.php' ) && defined( 'AKISMET_VERSION' ) ) && is_super_admin() ) {
+					$caps = array( bbp_admin()->minimum_capability );
+				} else {
+					$caps = array( 'do_not_allow' );
+				}
+
+				break;
+		}
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_map_settings_meta_caps', $caps, $cap, $user_id, $args );
+	}
+
+	/**
+	 * Register the importers
+	 *
+	 * @since 2.0.0 bbPress (r2737)
+	 */
+	public function register_importers() {
+
+		// Leave if we're not in the import section
+		if ( ! defined( 'WP_LOAD_IMPORTERS' ) ) {
+			return;
+		}
+
+		// Load Importer API
+		require_once ABSPATH . 'wp-admin/includes/import.php';
+
+		// Load our importers
+		$importers = apply_filters( 'bbp_importers', array( 'bbpress' ) );
+
+		// Loop through included importers
+		foreach ( $importers as $importer ) {
+
+			// Allow custom importer directory
+			$import_dir  = apply_filters( 'bbp_importer_path', $this->admin_dir . 'importers', $importer );
+
+			// Compile the importer path
+			$import_file = trailingslashit( $import_dir ) . $importer . '.php';
+
+			// If the file exists, include it
+			if ( file_exists( $import_file ) ) {
+				require $import_file;
+			}
+		}
+	}
+
+	/**
+	 * Add Settings link to plugins area
+	 *
+	 * @since 2.0.0 bbPress (r2737)
+	 *
+	 * @param array $links Links array in which we would prepend our link
+	 * @param string $file Current plugin basename
+	 * @return array Processed links
+	 */
+	public static function modify_plugin_action_links( $links, $file ) {
+
+		// Return normal links if not bbPress
+		if ( plugin_basename( bbpress()->basename ) !== $file ) {
+			return $links;
+		}
+
+		// New links to merge into existing links
+		$new_links = array();
+
+		// Settings page link
+		if ( current_user_can( 'bbp_settings_page' ) ) {
+			$new_links['settings'] = '<a href="' . esc_url( add_query_arg( array( 'page' => 'bbpress'   ), admin_url( 'options-general.php' ) ) ) . '">' . esc_html__( 'Settings', 'bbpress' ) . '</a>';
+		}
+
+		// About page link
+		if ( current_user_can( 'bbp_about_page' ) ) {
+			$new_links['about']    = '<a href="' . esc_url( add_query_arg( array( 'page' => 'bbp-about' ), admin_url( 'index.php'           ) ) ) . '">' . esc_html__( 'About',    'bbpress' ) . '</a>';
+		}
+
+		// Add a few links to the existing links array
+		return array_merge( $links, $new_links );
+	}
+
+	/**
+	 * Enqueue any admin scripts we might need
+	 *
+	 * @since 2.2.0 bbPress (r4260)
+	 */
+	public function enqueue_scripts() {
+
+		// Get the current screen
+		$current_screen = get_current_screen();
+
+		// Enqueue suggest for forum/topic/reply autocompletes
+		wp_enqueue_script( 'suggest' );
+
+		// Post type checker (only topics and replies)
+		if ( 'post' === $current_screen->base ) {
+			switch ( $current_screen->post_type ) {
+				case bbp_get_reply_post_type() :
+				case bbp_get_topic_post_type() :
+
+					// Enqueue the common JS
+					wp_enqueue_script( 'bbp-admin-common-js' );
+
+					// Topics admin
+					if ( bbp_get_topic_post_type() === $current_screen->post_type ) {
+						wp_enqueue_script( 'bbp-admin-topics-js' );
+
+					// Replies admin
+					} elseif ( bbp_get_reply_post_type() === $current_screen->post_type ) {
+						wp_enqueue_script( 'bbp-admin-replies-js' );
+					}
+
+					break;
+			}
+
+		// Enqueue the badge JS
+		} elseif ( in_array( $current_screen->id, array( 'dashboard_page_bbp-about', 'dashboard_page_bbp-credits' ), true ) ) {
+			wp_enqueue_script( 'bbp-admin-badge-js' );
+		}
+	}
+
+	/**
+	 * Enqueue any admin scripts we might need
+	 *
+	 * @since 2.6.0 bbPress (r5224)
+	 */
+	public function enqueue_styles() {
+		wp_enqueue_style( 'bbp-admin-css' );
+	}
+
+	/**
+	 * Remove the individual recount and converter menus.
+	 * They are grouped together by h2 tabs
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 */
+	public function admin_head() {
+
+		// Tools
+		foreach ( bbp_get_tools_admin_pages() as $tool ) {
+			remove_submenu_page( 'tools.php', $tool['page'] );
+		}
+
+		// About
+		remove_submenu_page( 'index.php', 'bbp-about'   );
+		remove_submenu_page( 'index.php', 'bbp-credits' );
+	}
+
+	/**
+	 * Registers the bbPress admin styling and color schemes
+	 *
+	 * Because wp-content can exist outside of the WordPress root, there is no
+	 * way to be certain what the relative path of admin images is.
+	 *
+	 * @since 2.6.0 bbPress (r2521)
+	 */
+	public function register_admin_styles() {
+
+		// RTL and/or minified
+		$suffix  = is_rtl() ? '-rtl' : '';
+		$suffix .= defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		// Get the version to use for JS
+		$version = bbp_get_version();
+
+		// Register admin CSS with dashicons dependency
+		wp_register_style( 'bbp-admin-css', $this->css_url . 'admin' . $suffix . '.css', array( 'dashicons' ), $version );
+
+		// Color schemes are not available when running out of src
+		if ( false !== strpos( plugin_basename( bbpress()->file ), 'src' ) ) {
+			return;
+		}
+
+		// Mint
+		wp_admin_css_color(
+			'bbp-mint',
+			esc_html_x( 'Mint', 'admin color scheme', 'bbpress' ),
+			$this->styles_url . 'mint/colors' . $suffix . '.css',
+			array( '#4f6d59', '#33834e', '#5FB37C', '#81c498' ),
+			array( 'base' => '#f1f3f2', 'focus' => '#fff', 'current' => '#fff' )
+		);
+
+		// Evergreen
+		wp_admin_css_color(
+			'bbp-evergreen',
+			esc_html_x( 'Evergreen', 'admin color scheme', 'bbpress' ),
+			$this->styles_url . 'evergreen/colors' . $suffix . '.css',
+			array( '#324d3a', '#446950', '#56b274', '#324d3a' ),
+			array( 'base' => '#f1f3f2', 'focus' => '#fff', 'current' => '#fff' )
+		);
+	}
+
+	/**
+	 * Registers the bbPress admin color schemes
+	 *
+	 * Because wp-content can exist outside of the WordPress root there is no
+	 * way to be certain what the relative path of the admin images is.
+	 * We are including the two most common configurations here, just in case.
+	 *
+	 * @since 2.6.0 bbPress (r2521)
+	 */
+	public function register_admin_scripts() {
+
+		// Minified
+		$suffix  = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		// Get the version to use for JS
+		$version = bbp_get_version();
+
+		// Header JS
+		wp_register_script( 'bbp-admin-common-js',  $this->js_url . 'common'    . $suffix . '.js', array( 'jquery', 'suggest'              ), $version );
+		wp_register_script( 'bbp-admin-topics-js',  $this->js_url . 'topics'    . $suffix . '.js', array( 'jquery'                         ), $version );
+		wp_register_script( 'bbp-admin-replies-js', $this->js_url . 'replies'   . $suffix . '.js', array( 'jquery', 'suggest'              ), $version );
+		wp_register_script( 'bbp-converter',        $this->js_url . 'converter' . $suffix . '.js', array( 'jquery', 'postbox', 'dashboard' ), $version );
+
+		// Footer JS
+		wp_register_script( 'bbp-admin-badge-js',   $this->js_url . 'badge' . $suffix . '.js', array(), $version, true );
+	}
+
+	/**
+	 * Allow keymaster role to save Forums settings
+	 *
+	 * @since 2.3.0 bbPress (r4678)
+	 *
+	 * @param string $capability
+	 * @return string Return minimum capability
+	 */
+	public function option_page_capability_bbpress( $capability = 'manage_options' ) {
+		$capability = $this->minimum_capability;
+		return $capability;
+	}
+
+	/** Ajax ******************************************************************/
+
+	/**
+	 * Ajax action for facilitating the forum auto-suggest
+	 *
+	 * @since 2.2.0 bbPress (r4261)
+	 */
+	public function suggest_topic() {
+
+		// Do some very basic request checking
+		$request = ! empty( $_REQUEST['q'] )
+			? trim( $_REQUEST['q'] )
+			: '';
+
+		// Bail early if empty request
+		if ( empty( $request ) ) {
+			wp_die();
+		}
+
+		// Bail if user cannot moderate
+		if ( ! current_user_can( 'moderate' ) ) {
+			wp_die();
+		}
+
+		// Check the ajax nonce
+		check_ajax_referer( 'bbp_suggest_topic_nonce' );
+
+		// Allow the maximum number of results to be filtered
+		$number = (int) apply_filters( 'bbp_suggest_topic_count', 10 );
+
+		// Try to get some topics
+		$topics = get_posts( array(
+			's'              => bbp_db()->esc_like( $_REQUEST['q'] ),
+			'post_type'      => bbp_get_topic_post_type(),
+			'posts_per_page' => $number,
+
+			// Performance
+			'nopaging'               => true,
+			'suppress_filters'       => true,
+			'update_post_term_cache' => false,
+			'update_post_meta_cache' => false,
+			'ignore_sticky_posts'    => true,
+			'no_found_rows'          => true
+		) );
+
+		// If we found some topics, loop through and display them
+		if ( ! empty( $topics ) ) {
+			foreach ( (array) $topics as $post ) {
+				printf( esc_html__( '%1$s - %2$s', 'bbpress' ), bbp_get_topic_id( $post->ID ), bbp_get_topic_title( $post->ID ) . "\n" );
+			}
+		}
+		die();
+	}
+
+	/**
+	 * Ajax action for facilitating the topic and reply author auto-suggest
+	 *
+	 * @since 2.4.0 bbPress (r5014)
+	 */
+	public function suggest_user() {
+
+		// Do some very basic request checking
+		$request = ! empty( $_REQUEST['q'] )
+			? trim( $_REQUEST['q'] )
+			: '';
+
+		// Bail early if empty request
+		if ( empty( $request ) ) {
+			wp_die();
+		}
+
+		// Bail if user cannot moderate
+		if ( ! current_user_can( 'moderate' ) ) {
+			wp_die();
+		}
+
+		// Check the ajax nonce
+		check_ajax_referer( 'bbp_suggest_user_nonce' );
+
+		// Fields to retrieve & search by
+		$fields = $search = array( 'ID', 'user_nicename' );
+
+		// Keymasters & Super-Mods can also search by email
+		if ( current_user_can( 'keep_gate' ) || bbp_allow_super_mods() ) {
+
+			// Add user_email to searchable columns
+			array_push( $search, 'user_email' );
+
+			// Unstrict to also allow some email characters
+			$strict = false;
+
+		// Strict sanitizing if not Keymaster or Super-Mod
+		} else {
+			$strict = true;
+		}
+
+		// Sanitize the request value (possibly not strictly)
+		$suggest = sanitize_user( $request, $strict );
+
+		// Bail if searching for invalid user string
+		if ( empty( $suggest ) ) {
+			wp_die();
+		}
+
+		// These single characters should not trigger a user query
+		$disallowed_single_chars = array( '@', '.', '_', '-', '+', '!', '#', '$', '%', '&', '\\', '*', '+', '/', '=', '?', '^', '`', '{', '|', '}', '~' );
+
+		// Bail if request is only for the above single characters
+		if ( in_array( $suggest, $disallowed_single_chars, true ) ) {
+			wp_die();
+		}
+
+		// Allow the maximum number of results to be filtered
+		$number = (int) apply_filters( 'bbp_suggest_user_count', 10 );
+
+		// Query database for users based on above criteria
+		$users_query = new WP_User_Query( array(
+			'search'         => '*' . bbp_db()->esc_like( $suggest ) . '*',
+			'fields'         => $fields,
+			'search_columns' => $search,
+			'orderby'        => 'ID',
+			'number'         => $number,
+			'count_total'    => false
+		) );
+
+		// If we found some users, loop through and output them to the AJAX
+		if ( ! empty( $users_query->results ) ) {
+			foreach ( (array) $users_query->results as $user ) {
+				printf( esc_html__( '%1$s - %2$s', 'bbpress' ), bbp_get_user_id( $user->ID ), bbp_get_user_nicename( $user->ID, array( 'force' => $user->user_nicename ) ) . "\n" );
+			}
+		}
+		die();
+	}
+
+	/** About *****************************************************************/
+
+	/**
+	 * Output the shared screen header for about_screen() & credits_screen()
+	 *
+	 * Contains title, subtitle, and badge area
+	 *
+	 * @since 2.6.0 bbPress (r6604)
+	 */
+	private function screen_header() {
+		list( $display_version ) = explode( '-', bbp_get_version() ); ?>
+
+		<h1 class="wp-heading-inline"><?php printf( esc_html__( 'Welcome to bbPress %s', 'bbpress' ), $display_version ); ?></h1>
+		<hr class="wp-header-end">
+		<div class="about-text"><?php printf( esc_html__( 'bbPress is fun to use, contains no artificial colors or preservatives, and is absolutely wonderful in every environment. Your community is going to love using it.', 'bbpress' ), $display_version ); ?></div>
+
+		<span class="bbp-hive" id="bbp-hive"></span>
+		<div class="bbp-badge" id="bbp-badge">
+			<span class="bbp-bee" id="bbp-bee"></span>
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the about screen
+	 *
+	 * @since 2.2.0 bbPress (r4159)
+	 *
+	 * @todo Host this remotely.
+	 */
+	public function about_screen() {
+		?>
+
+		<div class="wrap about-wrap">
+
+			<?php $this->screen_header(); ?>
+
+			<h2 class="nav-tab-wrapper">
+				<a class="nav-tab nav-tab-active" href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbp-about' ), admin_url( 'index.php' ) ) ); ?>">
+					<?php esc_html_e( 'What&#8217;s New', 'bbpress' ); ?>
+				</a><a class="nav-tab" href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbp-credits' ), admin_url( 'index.php' ) ) ); ?>">
+					<?php esc_html_e( 'Credits', 'bbpress' ); ?>
+				</a>
+			</h2>
+
+			<div class="changelog">
+				<h3><?php esc_html_e( 'Forum Subscriptions', 'bbpress' ); ?></h3>
+
+				<div class="feature-section col two-col">
+					<div class="last-feature">
+						<h4><?php esc_html_e( 'Subscribe to Forums', 'bbpress' ); ?></h4>
+						<p><?php esc_html_e( 'Now your users can subscribe to new topics in specific forums.', 'bbpress' ); ?></p>
+					</div>
+
+					<div>
+						<h4><?php esc_html_e( 'Manage Subscriptions', 'bbpress' ); ?></h4>
+						<p><?php esc_html_e( 'Your users can manage all of their subscriptions in one convenient location.', 'bbpress' ); ?></p>
+					</div>
+				</div>
+			</div>
+
+			<div class="changelog">
+				<h3><?php esc_html_e( 'Converters', 'bbpress' ); ?></h3>
+
+				<div class="feature-section col one-col">
+					<div class="last-feature">
+						<p><?php esc_html_e( 'We&#8217;re all abuzz about the hive of new importers, AEF, Drupal, FluxBB, Kunena Forums for Joomla, MyBB, Phorum, PHPFox, PHPWind, PunBB, SMF, Xenforo and XMB. Existing importers are now sweeter than honey with improved importing stickies, topic tags, forum categories and the sting is now gone if you need to remove imported users.', 'bbpress' ); ?></p>
+					</div>
+				</div>
+
+				<div class="feature-section col three-col">
+					<div>
+						<h4><?php esc_html_e( 'Theme Compatibility', 'bbpress' ); ?></h4>
+						<p><?php esc_html_e( 'Better handling of styles and scripts in the template stack.', 'bbpress' ); ?></p>
+					</div>
+
+					<div>
+						<h4><?php esc_html_e( 'Polyglot support', 'bbpress' ); ?></h4>
+						<p><?php esc_html_e( 'bbPress fully supports automatic translation updates.', 'bbpress' ); ?></p>
+					</div>
+
+					<div class="last-feature">
+						<h4><?php esc_html_e( 'User capabilities', 'bbpress' ); ?></h4>
+						<p><?php esc_html_e( 'Roles and capabilities have been swept through, cleaned up, and simplified.', 'bbpress' ); ?></p>
+					</div>
+				</div>
+			</div>
+
+			<div class="return-to-dashboard">
+				<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbpress' ), admin_url( 'options-general.php' ) ) ); ?>"><?php esc_html_e( 'Go to Forum Settings', 'bbpress' ); ?></a>
+			</div>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the credits screen
+	 *
+	 * @since 2.2.0 bbPress (r4159)
+	 *
+	 * @todo Host this remotely.
+	 */
+	public function credits_screen() {
+		?>
+
+		<div class="wrap about-wrap">
+
+			<?php $this->screen_header(); ?>
+
+			<h2 class="nav-tab-wrapper">
+				<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbp-about' ), admin_url( 'index.php' ) ) ); ?>" class="nav-tab">
+					<?php esc_html_e( 'What&#8217;s New', 'bbpress' ); ?>
+				</a><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbp-credits' ), admin_url( 'index.php' ) ) ); ?>" class="nav-tab nav-tab-active">
+					<?php esc_html_e( 'Credits', 'bbpress' ); ?>
+				</a>
+			</h2>
+
+			<p class="about-description"><?php esc_html_e( 'bbPress is created by a worldwide swarm of busy, busy bees.', 'bbpress' ); ?></p>
+
+			<h3 class="wp-people-group"><?php esc_html_e( 'Project Leaders', 'bbpress' ); ?></h3>
+			<ul class="wp-people-group " id="wp-people-group-project-leaders">
+				<li class="wp-person" id="wp-person-matt">
+					<a href="https://profiles.wordpress.org/matt" class="web"><img src="http://0.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=120" class="gravatar" alt="" />Matt Mullenweg</a>
+					<span class="title"><?php esc_html_e( 'Founding Developer', 'bbpress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-johnjamesjacoby">
+					<a href="https://profiles.wordpress.org/johnjamesjacoby" class="web"><img src="http://0.gravatar.com/avatar/7a2644fb53ae2f7bfd7143b504af396c?s=120" class="gravatar" alt="" />John James Jacoby</a>
+					<span class="title"><?php esc_html_e( 'Lead Developer', 'bbpress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-jmdodd">
+					<a href="https://profiles.wordpress.org/jmdodd" class="web"><img src="http://0.gravatar.com/avatar/6a7c997edea340616bcc6d0fe03f65dd?s=120" class="gravatar" alt="" />Jennifer M. Dodd</a>
+					<span class="title"><?php esc_html_e( 'Feature Virtuoso', 'bbpress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-netweb">
+					<a href="https://profiles.wordpress.org/netweb" class="web"><img src="http://0.gravatar.com/avatar/97e1620b501da675315ba7cfb740e80f?s=120" class="gravatar" alt="" />Stephen Edgar</a>
+					<span class="title"><?php esc_html_e( 'Tool Maven', 'bbpress' ); ?></span>
+				</li>
+			</ul>
+
+			<h3 class="wp-people-group"><?php esc_html_e( 'Contributing Developers', 'bbpress' ); ?></h3>
+			<ul class="wp-people-group " id="wp-people-group-contributing-developers">
+				<li class="wp-person" id="wp-person-sergeybiryukov">
+					<a href="https://profiles.wordpress.org/SergeyBiryukov" class="web"><img src="http://0.gravatar.com/avatar/750b7b0fcd855389264c2b1294d61bd6?s=120" class="gravatar" alt="" />Sergey Biryukov</a>
+					<span class="title"><?php esc_html_e( 'Core Developer', 'bbpress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-thebrandonallen">
+					<a href="https://profiles.wordpress.org/thebrandonallen" class="web"><img src="http://0.gravatar.com/avatar/6d3f77bf3c9ca94c406dea401b566950?s?s=120" class="gravatar" alt="" />Brandon Allen</a>
+					<span class="title"><?php esc_html_e( 'Core Developer', 'bbpress' ); ?></span>
+				</li>
+			</ul>
+
+			<h3 class="wp-people-group"><?php esc_html_e( 'Project Emeriti', 'bbpress' ); ?></h3>
+			<ul class="wp-people-group " id="wp-people-group-project-emeriti">
+				<li class="wp-person" id="wp-person-gautamgupta">
+					<a href="https://profiles.wordpress.org/gautamgupta" class="web"><img src="http://0.gravatar.com/avatar/b0810422cbe6e4eead4def5ae7a90b34?s=120" class="gravatar" alt="" />Gautam Gupta</a>
+					<span class="title"><?php esc_html_e( 'Feature Developer', 'bbpress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-jaredatch">
+					<a href="https://profiles.wordpress.org/jaredatch" class="web"><img src="http://0.gravatar.com/avatar/e341eca9e1a85dcae7127044301b4363?s=120" class="gravatar" alt="" />Jared Atchison</a>
+					<span class="title"><?php esc_html_e( 'Integration Testing', 'bbpress' ); ?></span>
+				</li>
+			</ul>
+
+			<h3 class="wp-people-group"><?php esc_html_e( 'Contributors to bbPress 2.6', 'bbpress' ); ?></h3>
+			<p class="wp-credits-list">
+				<a href="https://profiles.wordpress.org/alex-ye">alex-ye</a>,
+				<a href="https://profiles.wordpress.org/ankit-k-gupta">ankit-k-gupta</a>,
+				<a href="https://profiles.wordpress.org/barryhughes-1">barryhughes-1</a>,
+				<a href="https://profiles.wordpress.org/boonebgorges">boonebgorges</a>,
+				<a href="https://profiles.wordpress.org/casiepa">casiepa</a>,
+				<a href="https://profiles.wordpress.org/cfinke">cfinke</a>,
+				<a href="https://profiles.wordpress.org/danielbachhuber">danielbachhuber</a>,
+				<a href="https://profiles.wordpress.org/dimitrovadrian">dimitrov.adrian</a>,
+				<a href="https://profiles.wordpress.org/DJPaul">DJPaul</a>,
+				<a href="https://profiles.wordpress.org/DrPepper75">DrPepper75</a>,
+				<a href="https://profiles.wordpress.org/eoigal">eoigal</a>,
+				<a href="https://profiles.wordpress.org/ericlewis">ericlewis</a>,
+				<a href="https://profiles.wordpress.org/extendwings">extendwings</a>,
+				<a href="https://profiles.wordpress.org/Faison">Faison</a>,
+				<a href="https://profiles.wordpress.org/gautamgupta">gautamgupta</a>,
+				<a href="https://profiles.wordpress.org/glynwintle">glynwintle</a>,
+				<a href="https://profiles.wordpress.org/gusrb84">gusrb84</a>,
+				<a href="https://profiles.wordpress.org/hellofromTonya">hellofromTonya</a>,
+				<a href="https://profiles.wordpress.org/icu0755">icu0755</a>,
+				<a href="https://profiles.wordpress.org/imath">imath</a>,
+				<a href="https://profiles.wordpress.org/jbrinley">jbrinley</a>,
+				<a href="https://profiles.wordpress.org/jdgrimes">jdgrimes</a>,
+				<a href="https://profiles.wordpress.org/jmdodd">jmdodd</a>,
+				<a href="https://profiles.wordpress.org/joedolson">joedolson</a>,
+				<a href="https://profiles.wordpress.org/johnbillion">johnbillion</a>,
+				<a href="https://profiles.wordpress.org/johnjamesjacoby">johnjamesjacoby</a>,
+				<a href="https://profiles.wordpress.org/jorbin">jorbin</a>,
+				<a href="https://profiles.wordpress.org/jreeve">jreeve</a>,
+				<a href="https://profiles.wordpress.org/kadamwhite ">kadamwhite</a>,
+				<a href="https://profiles.wordpress.org/karlgroves">karlgroves</a>,
+				<a href="https://profiles.wordpress.org/mat-lipe">mat-lipe</a>,
+				<a href="https://profiles.wordpress.org/mazengamal">mazengamal</a>,
+				<a href="https://profiles.wordpress.org/melchoyce">melchoyce</a>,
+				<a href="https://profiles.wordpress.org/mercime">mercime</a>,
+				<a href="https://profiles.wordpress.org/michaelbeil">michaelbeil</a>,
+				<a href="https://profiles.wordpress.org/mikelopez">mikelopez</a>,
+				<a href="https://profiles.wordpress.org/mordauk">mordauk</a>,
+				<a href="https://profiles.wordpress.org/mspecht">mspecht</a>,
+				<a href="https://profiles.wordpress.org/MZAWeb">MZAWeb</a>,
+				<a href="https://profiles.wordpress.org/netweb">netweb</a>,
+				<a href="https://profiles.wordpress.org/ocean90">ocean90</a>,
+				<a href="https://profiles.wordpress.org/offereins">offereins</a>,
+				<a href="https://profiles.wordpress.org/pareshradadiya">pareshradadiya</a>,
+				<a href="https://profiles.wordpress.org/r-a-y">r-a-y</a>,
+				<a href="https://profiles.wordpress.org/ramiy">ramiy</a>,
+				<a href="https://profiles.wordpress.org/robin-w">robin-w</a>,
+				<a href="https://profiles.wordpress.org/robkk">robkk</a>,
+				<a href="https://profiles.wordpress.org/ryelle">ryelle</a>,
+				<a href="https://profiles.wordpress.org/satollo">satollo</a>,
+				<a href="https://profiles.wordpress.org/SergeyBiryukov">Sergey Biryukov</a>,
+				<a href="https://profiles.wordpress.org/SGr33n">SGr33n</a>,
+				<a href="https://profiles.wordpress.org/stephdau">stephdau</a>,
+				<a href="https://profiles.wordpress.org/tharsheblows">tharsheblows</a>,
+				<a href="https://profiles.wordpress.org/thebrandonallen">thebrandonallen</a>,
+				<a href="https://profiles.wordpress.org/tobyhawkins">tobyhawkins</a>,
+				<a href="https://profiles.wordpress.org/tonyrix">tonyrix</a>,
+				<a href="https://profiles.wordpress.org/treyhunner">treyhunner</a>,
+				<a href="https://profiles.wordpress.org/tw2113">tw2113</a>,
+				<a href="https://profiles.wordpress.org/xknown">xknown</a>
+			</p>
+
+			<div class="return-to-dashboard">
+				<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bbpress' ), admin_url( 'options-general.php' ) ) ); ?>"><?php esc_html_e( 'Go to Forum Settings', 'bbpress' ); ?></a>
+			</div>
+
+		</div>
+
+		<?php
+	}
+
+	/** Updaters **************************************************************/
+
+	/**
+	 * Update all bbPress forums across all sites
+	 *
+	 * @since 2.1.0 bbPress (r3689)
+	 */
+	public static function update_screen() {
+
+		// Get action
+		$action = isset( $_GET['action'] ) ? $_GET['action'] : ''; ?>
+
+		<div class="wrap">
+			<h1 class="wp-heading-inline"><?php esc_html_e( 'Update Forum', 'bbpress' ); ?></h1>
+			<hr class="wp-header-end">
+
+		<?php
+
+		// Taking action
+		switch ( $action ) {
+			case 'bbp-update' :
+
+				// Run the full updater
+				bbp_version_updater(); ?>
+
+				<p><?php esc_html_e( 'All done!', 'bbpress' ); ?></p>
+				<a class="button" href="index.php?page=bbp-update"><?php esc_html_e( 'Go Back', 'bbpress' ); ?></a>
+
+				<?php
+
+				break;
+
+			case 'show' :
+			default : ?>
+
+				<p><?php esc_html_e( 'You can update your forum through this page. Hit the link below to update.', 'bbpress' ); ?></p>
+				<p><a class="button" href="index.php?page=bbp-update&amp;action=bbp-update"><?php esc_html_e( 'Update Forum', 'bbpress' ); ?></a></p>
+
+			<?php break;
+
+		} ?>
+
+		</div><?php
+	}
+
+	/**
+	 * Update all bbPress forums across all sites
+	 *
+	 * @since 2.1.0 bbPress (r3689)
+	 */
+	public static function network_update_screen() {
+		$bbp_db = bbp_db();
+
+		// Get action
+		$action = isset( $_GET['action'] ) ? $_GET['action'] : ''; ?>
+
+		<div class="wrap">
+			<h1 class="wp-heading-inline"><?php esc_html_e( 'Update Forums', 'bbpress' ); ?></h1>
+			<hr class="wp-header-end">
+
+		<?php
+
+		// Taking action
+		switch ( $action ) {
+			case 'bbpress-update' :
+
+				// Site counter
+				$n = isset( $_GET['n'] ) ? intval( $_GET['n'] ) : 0;
+
+				// Get blogs 5 at a time
+				$blogs = $bbp_db->get_results( "SELECT * FROM {$bbp_db->blogs} WHERE site_id = '{$bbp_db->siteid}' AND spam = '0' AND deleted = '0' AND archived = '0' ORDER BY registered DESC LIMIT {$n}, 5", ARRAY_A );
+
+				// No blogs so all done!
+				if ( empty( $blogs ) ) : ?>
+
+					<p><?php esc_html_e( 'All done!', 'bbpress' ); ?></p>
+					<a class="button" href="update-core.php?page=bbpress-update"><?php esc_html_e( 'Go Back', 'bbpress' ); ?></a>
+
+				<?php
+
+				// Still have sites to loop through
+				else : ?>
+
+					<ul>
+
+						<?php foreach ( (array) $blogs as $details ) :
+
+							// Get site URLs
+							$site_url   = get_site_url( $details['blog_id'] );
+							$admin_url  = get_site_url( $details['blog_id'], 'wp-admin.php', 'admin' );
+							$remote_url = add_query_arg( array(
+								'page'   => 'bbp-update',
+								'action' => 'bbp-update'
+							), $admin_url ); ?>
+
+							<li><?php echo esc_html( $site_url ); ?></li>
+
+							<?php
+
+							// Get the response of the bbPress update on this site
+							$response = wp_remote_get(
+								$remote_url,
+								array(
+									'timeout'     => 30,
+									'httpversion' => '1.1'
+								)
+							);
+
+							// Site errored out, no response?
+							if ( is_wp_error( $response ) ) {
+								wp_die( sprintf( esc_html__( 'Warning! Problem updating %1$s. Your server may not be able to connect to sites running on it. Error message: %2$s', 'bbpress' ), $site_url, '<em>' . $response->get_error_message() . '</em>' ) );
+							}
+
+							// Switch to the new site
+							bbp_switch_to_site( $details[ 'blog_id' ] );
+
+							$basename = bbpress()->basename;
+
+							// Run the updater on this site
+							if ( is_plugin_active_for_network( $basename ) || is_plugin_active( $basename ) ) {
+								bbp_version_updater();
+							}
+
+							// Restore original site
+							bbp_restore_current_site();
+
+							// Do some actions to allow plugins to do things too
+							do_action( 'after_bbpress_upgrade', $response             );
+							do_action( 'bbp_upgrade_site',      $details[ 'blog_id' ] );
+
+						endforeach; ?>
+
+					</ul>
+
+					<p>
+						<?php esc_html_e( 'If your browser doesn&#8217;t start loading the next page automatically, click this link:', 'bbpress' ); ?>
+						<a class="button" href="update-core.php?page=bbpress-update&amp;action=bbpress-update&amp;n=<?php echo ( $n + 5 ); ?>"><?php esc_html_e( 'Next Forums', 'bbpress' ); ?></a>
+					</p>
+					<script type='text/javascript'>
+						<!--
+						function nextpage() {
+							location.href = 'update-core.php?page=bbpress-update&action=bbpress-update&n=<?php echo ( $n + 5 ) ?>';
+						}
+						setTimeout( 'nextpage()', 250 );
+						//-->
+					</script><?php
+
+				endif;
+
+				break;
+
+			case 'show' :
+			default : ?>
+
+				<p><?php esc_html_e( 'You can update all the forums on your network through this page. It works by calling the update script of each site automatically. Hit the link below to update.', 'bbpress' ); ?></p>
+				<p><a class="button" href="update-core.php?page=bbpress-update&amp;action=bbpress-update"><?php esc_html_e( 'Update Forums', 'bbpress' ); ?></a></p>
+
+			<?php break;
+
+		} ?>
+
+		</div><?php
+	}
+}
+endif; // class_exists check
Index: /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-base.php
===================================================================
--- /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-base.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-base.php	(revision 7162)
@@ -0,0 +1,1226 @@
+<?php
+
+/**
+ * bbPress Converter Base Class
+ *
+ * Based on the hard work of Adam Ellis
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Converter_Base' ) ) :
+/**
+ * Base class to be extended by specific individual importers
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+abstract class BBP_Converter_Base {
+
+	/**
+	 * @var array() This is the field mapping array to process.
+	 */
+	protected $field_map = array();
+
+	/**
+	 * @var object This is the connection to the WordPress database.
+	 */
+	protected $wpdb;
+
+	/**
+	 * @var object This is the connection to the other platforms database.
+	 */
+	protected $opdb;
+
+	/**
+	 * @var int Maximum number of rows to convert at 1 time. Default 100.
+	 */
+	protected $max_rows = 100;
+
+	/**
+	 * @var array() Map of topic to forum.  It is for optimization.
+	 */
+	protected $map_topicid_to_forumid = array();
+
+	/**
+	 * @var array() Map of from old forum ids to new forum ids.  It is for optimization.
+	 */
+	protected $map_forumid = array();
+
+	/**
+	 * @var array() Map of from old topic ids to new topic ids.  It is for optimization.
+	 */
+	protected $map_topicid = array();
+
+	/**
+	 * @var array() Map of from old reply_to ids to new reply_to ids.  It is for optimization.
+	 */
+	protected $map_reply_to = array();
+
+	/**
+	 * @var array() Map of from old user ids to new user ids.  It is for optimization.
+	 */
+	protected $map_userid = array();
+
+	/**
+	 * @var str This is the charset for your wp database.
+	 */
+	public $charset = '';
+
+	/**
+	 * @var boolean Sync table available.
+	 */
+	public $sync_table = false;
+
+	/**
+	 * @var str Sync table name.
+	 */
+	public $sync_table_name = '';
+
+	/**
+	 * @var bool Whether users should be converted or not. Default false.
+	 */
+	public $convert_users = false;
+
+	/**
+	 * @var bool Whether to clean up any old converter data. Default false.
+	 */
+	public $clean = false;
+
+	/**
+	 * @var array Custom BBCode class properties in a key => value format
+	 */
+	public $bbcode_parser_properties = array();
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * This is the constructor and it connects to the platform databases.
+	 */
+	public function __construct() {
+		$this->init();
+		$this->setup_globals();
+	}
+
+	/**
+	 * Initialize the converter
+	 *
+	 * @since 2.1.0
+	 */
+	private function init() {
+
+		/** BBCode Parse Properties *******************************************/
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => includes_url( 'images/smilies' ),
+			'smiley_dir' => '/' . WPINC . '/images/smilies'
+		);
+
+		/** Sanitize Options **************************************************/
+
+		$this->clean         = ! empty( $_POST['_bbp_converter_clean'] );
+		$this->convert_users = (bool) get_option( '_bbp_converter_convert_users', false );
+		$this->halt          = (bool) get_option( '_bbp_converter_halt',          0     );
+		$this->max_rows      = (int)  get_option( '_bbp_converter_rows',          100   );
+
+		/** Sanitize Connection ***********************************************/
+
+		$db_user   = get_option( '_bbp_converter_db_user',   DB_USER     );
+		$db_pass   = get_option( '_bbp_converter_db_pass',   DB_PASSWORD );
+		$db_name   = get_option( '_bbp_converter_db_name',   DB_NAME     );
+		$db_host   = get_option( '_bbp_converter_db_server', DB_HOST     );
+		$db_port   = get_option( '_bbp_converter_db_port',   ''          );
+		$db_prefix = get_option( '_bbp_converter_db_prefix', ''          );
+
+		// Maybe add port to server
+		if ( ! empty( $db_port ) && ! empty( $db_host ) && ! strstr( $db_host, ':' ) ) {
+			$db_host = "{$db_host}:{$db_port}";
+		}
+
+		/** Get database connections ******************************************/
+
+		// Setup WordPress Database
+		$this->wpdb = bbp_db();
+
+		// Setup old forum Database
+		$this->opdb = new BBP_Converter_DB( $db_user, $db_pass, $db_name, $db_host );
+
+		// Connection failed
+		if ( ! $this->opdb->db_connect( false ) ) {
+			$error = new WP_Error( 'bbp_converter_db_connection_failed', esc_html__( 'Database connection failed.', 'bbpress' ) );
+			wp_send_json_error( $error );
+		}
+
+		// Maybe setup the database prefix
+		$this->opdb->prefix = $db_prefix;
+
+		/**
+		 * Don't wp_die() uncontrollably
+		 */
+		$this->wpdb->show_errors( false );
+		$this->opdb->show_errors( false );
+
+		/**
+		 * Syncing
+		 */
+		$this->sync_table_name = $this->wpdb->prefix . 'bbp_converter_translator';
+		$this->sync_table      = $this->sync_table_name === $this->wpdb->get_var( "SHOW TABLES LIKE '{$this->sync_table_name}'" )
+			? true
+			: false;
+
+		/**
+		 * Character set
+		 */
+		$this->charset = ! empty( $this->wpdb->charset )
+			? $this->wpdb->charset
+			: 'UTF8';
+
+		/**
+		 * Default mapping.
+		 */
+
+		/** Forum Section *****************************************************/
+
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_status',
+			'default'      => 'publish'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'comment_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'ping_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_type',
+			'default'      => 'forum'
+		);
+
+		/** Topic Section *****************************************************/
+
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => 'post_status',
+			'default'      => 'publish'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => 'comment_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => 'ping_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => 'post_type',
+			'default'      => 'topic'
+		);
+
+		/** Post Section ******************************************************/
+
+		$this->field_map[] = array(
+			'to_type'      => 'reply',
+			'to_fieldname' => 'post_status',
+			'default'      => 'publish'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'reply',
+			'to_fieldname' => 'comment_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'reply',
+			'to_fieldname' => 'ping_status',
+			'default'      => 'closed'
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'reply',
+			'to_fieldname' => 'post_type',
+			'default'      => 'reply'
+		);
+
+		/** User Section ******************************************************/
+
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => 'role',
+			'default'      => get_option( 'default_role' )
+		);
+	}
+
+	/**
+	 * Setup global values
+	 */
+	public function setup_globals() {}
+
+	/**
+	 * Convert Forums
+	 */
+	public function convert_forums( $start = 1 ) {
+		return $this->convert_table( 'forum', $start );
+	}
+
+	/**
+	 * Convert Topics / Threads
+	 */
+	public function convert_topics( $start = 1 ) {
+		return $this->convert_table( 'topic', $start );
+	}
+
+	/**
+	 * Convert Posts
+	 */
+	public function convert_replies( $start = 1 ) {
+		return $this->convert_table( 'reply', $start );
+	}
+
+	/**
+	 * Convert Users
+	 */
+	public function convert_users( $start = 1 ) {
+		return $this->convert_table( 'user', $start );
+	}
+
+	/**
+	 * Convert Topic Tags
+	 */
+	public function convert_tags( $start = 1 ) {
+		return $this->convert_table( 'tags', $start );
+	}
+
+	/**
+	 * Convert Forum Subscriptions
+	 */
+	public function convert_forum_subscriptions( $start = 1 ) {
+		return $this->convert_table( 'forum_subscriptions', $start );
+	}
+
+	/**
+	 * Convert Topic Subscriptions
+	 */
+	public function convert_topic_subscriptions( $start = 1 ) {
+		return $this->convert_table( 'topic_subscriptions', $start );
+	}
+
+	/**
+	 * Convert Favorites
+	 */
+	public function convert_favorites( $start = 1 ) {
+		return $this->convert_table( 'favorites', $start );
+	}
+
+	/**
+	 * Convert Table
+	 *
+	 * @param string to type
+	 * @param int Start row
+	 */
+	public function convert_table( $to_type, $start ) {
+
+		// Set some defaults
+		$has_insert     = false;
+		$from_tablename = '';
+		$field_list     = $from_tables = $tablefield_array = array();
+
+		// Toggle Table Name based on $to_type (destination)
+		switch ( $to_type ) {
+			case 'user' :
+				$tablename = $this->wpdb->users;
+				break;
+
+			case 'tags' :
+				$tablename = '';
+				break;
+
+			case 'forum_subscriptions' :
+				$tablename = $this->wpdb->postmeta;
+				break;
+
+			case 'topic_subscriptions' :
+				$tablename = $this->wpdb->postmeta;
+				break;
+
+			case 'favorites' :
+				$tablename = $this->wpdb->postmeta;
+				break;
+
+			default :
+				$tablename = $this->wpdb->posts;
+		}
+
+		// Get the fields from the destination table
+		if ( ! empty( $tablename ) ) {
+			$tablefield_array = $this->get_fields( $tablename );
+		}
+
+		/** Step 1 ************************************************************/
+
+		// Loop through the field maps, and look for to_type matches
+		foreach ( $this->field_map as $item ) {
+
+			// Yay a match, and we have a from table, too
+			if ( ( $item['to_type'] === $to_type ) && ! empty( $item['from_tablename'] ) ) {
+
+				// $from_tablename was set from a previous loop iteration
+				if ( ! empty( $from_tablename ) ) {
+
+					// Doing some joining
+					if ( ! in_array( $item['from_tablename'], $from_tables, true ) && in_array( $item['join_tablename'], $from_tables, true ) ) {
+						$from_tablename .= ' ' . $item['join_type'] . ' JOIN ' . $this->opdb->prefix . $item['from_tablename'] . ' AS ' . $item['from_tablename'] . ' ' . $item['join_expression'];
+					}
+
+				// $from_tablename needs to be set
+				} else {
+					$from_tablename = $item['from_tablename'] . ' AS ' . $item['from_tablename'];
+				}
+
+				// Specific FROM expression data used
+				if ( ! empty( $item['from_expression'] ) ) {
+
+					// No 'WHERE' in expression
+					if ( stripos( $from_tablename, "WHERE" ) === false ) {
+						$from_tablename .= ' ' . $item['from_expression'];
+
+					// 'WHERE' in expression, so replace with 'AND'
+					} else {
+						$from_tablename .= ' ' . str_replace( "WHERE", "AND", $item['from_expression'] );
+					}
+				}
+
+				// Add tablename and fieldname to arrays, formatted for querying
+				$from_tables[] = $item['from_tablename'];
+				$field_list[]  = 'convert(' . $item['from_tablename'] . '.' . $item['from_fieldname'] . ' USING "' . $this->charset . '") AS ' . $item['from_fieldname'];
+			}
+		}
+
+		/** Step 2 ************************************************************/
+
+		// We have a $from_tablename, so we want to get some data to convert
+		if ( ! empty( $from_tablename ) ) {
+
+			// Update rows
+			$this->count_rows_by_table( "{$this->opdb->prefix}{$from_tablename}" );
+
+			// Get some data from the old forums
+			$field_list  = array_unique( $field_list );
+			$fields      = implode( ',', $field_list );
+			$forum_query = "SELECT {$fields} FROM {$this->opdb->prefix}{$from_tablename} LIMIT {$start}, {$this->max_rows}";
+
+			// Set this query as the last one ran
+			$this->update_query( $forum_query );
+
+			// Get results as an array
+			$forum_array = $this->opdb->get_results( $forum_query, ARRAY_A );
+
+			// Query returned some results
+			if ( ! empty( $forum_array ) ) {
+
+				// Loop through results
+				foreach ( (array) $forum_array as $forum ) {
+
+					// Reset some defaults
+					$insert_post = $insert_postmeta = $insert_data = array();
+
+					// Loop through field map, again...
+					foreach ( $this->field_map as $row ) {
+
+						// Types match and to_fieldname is present. This means
+						// we have some work to do here.
+						if ( ( $row['to_type'] === $to_type ) && isset( $row['to_fieldname'] ) ) {
+
+							// This row has a destination that matches one of the
+							// columns in this table.
+							if ( in_array( $row['to_fieldname'], $tablefield_array, true ) ) {
+
+								// Allows us to set default fields.
+								if ( isset( $row['default'] ) ) {
+									$insert_post[ $row['to_fieldname'] ] = $row['default'];
+
+								// Translates a field from the old forum.
+								} elseif ( isset( $row['callback_method'] ) ) {
+									if ( ( 'callback_userid' === $row['callback_method'] ) && ( false === $this->convert_users ) ) {
+										$insert_post[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
+									} else {
+										$insert_post[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
+									}
+
+								// Maps the field from the old forum.
+								} else {
+									$insert_post[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
+								}
+
+							// Destination field is not empty, so we might need
+							// to do some extra work or set a default.
+							} elseif ( ! empty( $row['to_fieldname'] ) ) {
+
+								// Allows us to set default fields.
+								if ( isset( $row['default'] ) ) {
+									$insert_postmeta[ $row['to_fieldname'] ] = $row['default'];
+
+								// Translates a field from the old forum.
+								} elseif ( isset( $row['callback_method'] ) ) {
+									if ( ( $row['callback_method'] === 'callback_userid' ) && ( false === $this->convert_users ) ) {
+										$insert_postmeta[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
+									} else {
+										$insert_postmeta[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
+									}
+
+								// Maps the field from the old forum.
+								} else {
+									$insert_postmeta[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
+								}
+							}
+						}
+					}
+
+					/** Step 3 ************************************************/
+
+					// Something to insert into the destination field
+					if ( count( $insert_post ) > 0 || ( $to_type == 'tags' && count( $insert_postmeta ) > 0 ) ) {
+
+						switch ( $to_type ) {
+
+							/** New user **************************************/
+
+							case 'user' :
+								if ( username_exists( $insert_post['user_login'] ) ) {
+									$insert_post['user_login'] = "imported_{$insert_post['user_login']}";
+								}
+
+								if ( email_exists( $insert_post['user_email'] ) ) {
+									$insert_post['user_email'] = "imported_{$insert_post['user_email']}";
+								}
+
+								if ( empty( $insert_post['user_pass'] ) ) {
+									$insert_post['user_pass'] = '';
+								}
+
+								// Internally re-calls _exists() checks above.
+								// Also checks for existing nicename.
+								$post_id = wp_insert_user( $insert_post );
+
+								if ( is_numeric( $post_id ) ) {
+									foreach ( $insert_postmeta as $key => $value ) {
+										add_user_meta( $post_id, $key, $value, true );
+
+										if ( '_id' == substr( $key, -3 ) && ( true === $this->sync_table ) ) {
+											$this->wpdb->insert( $this->sync_table_name, array(
+												'value_type' => 'user',
+												'value_id'   => $post_id,
+												'meta_key'   => $key,
+												'meta_value' => $value
+											) );
+										}
+									}
+								}
+								break;
+
+							/** New Topic-Tag *********************************/
+
+							case 'tags' :
+								$post_id = wp_set_object_terms( $insert_postmeta['objectid'], $insert_postmeta['name'], 'topic-tag', true );
+								$term = get_term_by( 'name', $insert_postmeta['name'], 'topic-tag');
+								if ( false !== $term ) {
+									wp_update_term( $term->term_id, 'topic-tag', array(
+										'description' => $insert_postmeta['description'],
+										'slug'        => $insert_postmeta['slug']
+									) );
+								}
+								break;
+
+							/** Forum Subscriptions ***************************/
+
+							case 'forum_subscriptions' :
+								$user_id = $insert_post['user_id'];
+								$items   = wp_list_pluck( $insert_postmeta, '_bbp_forum_subscriptions' );
+								if ( is_numeric( $user_id ) && ! empty( $items ) ) {
+									foreach ( $items as $value ) {
+
+										// Maybe string with commas
+										$value = is_string( $value )
+											? explode( ',', $value )
+											: (array) $value;
+
+										// Add user ID to forums subscribed users
+										foreach ( $value as $fav ) {
+											bbp_add_user_subscription( $user_id, $this->callback_forumid( $fav ) );
+										}
+									}
+								}
+								break;
+
+							/** Subscriptions *********************************/
+
+							case 'topic_subscriptions' :
+								$user_id = $insert_post['user_id'];
+								$items   = wp_list_pluck( $insert_postmeta, '_bbp_subscriptions' );
+								if ( is_numeric( $user_id ) && ! empty( $items ) ) {
+									foreach ( $items as $value ) {
+
+										// Maybe string with commas
+										$value = is_string( $value )
+											? explode( ',', $value )
+											: (array) $value;
+
+										// Add user ID to topics subscribed users
+										foreach ( $value as $fav ) {
+											bbp_add_user_subscription( $user_id, $this->callback_topicid( $fav ) );
+										}
+									}
+								}
+								break;
+
+							/** Favorites *************************************/
+
+							case 'favorites' :
+								$user_id = $insert_post['user_id'];
+								$items   = wp_list_pluck( $insert_postmeta, '_bbp_favorites' );
+								if ( is_numeric( $user_id ) && ! empty( $items ) ) {
+									foreach ( $items as $value ) {
+
+										// Maybe string with commas
+										$value = is_string( $value )
+											? explode( ',', $value )
+											: (array) $value;
+
+										// Add user ID to topics favorited users
+										foreach ( $value as $fav ) {
+											bbp_add_user_favorite( $user_id, $this->callback_topicid( $fav ) );
+										}
+									}
+								}
+								break;
+
+							/** Forum, Topic, Reply ***************************/
+
+							default :
+								$post_id = wp_insert_post( $insert_post, true );
+
+								if ( is_numeric( $post_id ) ) {
+									foreach ( $insert_postmeta as $key => $value ) {
+										add_post_meta( $post_id, $key, $value, true );
+
+										/**
+										 * If we are using the sync_table add
+										 * the meta '_id' keys to the table
+										 *
+										 * Forums:  _bbp_old_forum_id         // The old forum ID
+										 *          _bbp_old_forum_parent_id  // The old forum parent ID
+										 *
+										 * Topics:  _bbp_forum_id             // The new forum ID
+										 *          _bbp_old_topic_id         // The old topic ID
+										 *          _bbp_old_closed_status_id // The old topic open/closed status
+										 *          _bbp_old_sticky_status_id // The old topic sticky status
+										 *
+										 * Replies: _bbp_forum_id             // The new forum ID
+										 *          _bbp_topic_id             // The new topic ID
+										 *          _bbp_old_reply_id         // The old reply ID
+										 *          _bbp_old_reply_to_id      // The old reply to ID
+										 */
+										if ( '_id' === substr( $key, -3 ) && ( true === $this->sync_table ) ) {
+											$this->wpdb->insert( $this->sync_table_name, array(
+												'value_type' => 'post',
+												'value_id'   => $post_id,
+												'meta_key'   => $key,
+												'meta_value' => $value
+											) );
+										}
+
+										/**
+										 * Replies need to save their old reply_to ID for
+										 * hierarchical replies association. Later we update
+										 * the _bbp_reply_to value with the new bbPress
+										 * value using convert_reply_to_parents()
+										 */
+										if ( ( 'reply' === $to_type ) && ( '_bbp_old_reply_to_id' === $key ) ) {
+											add_post_meta( $post_id, '_bbp_reply_to', $value );
+										}
+									}
+								}
+								break;
+						}
+						$has_insert = true;
+					}
+				}
+			}
+		}
+
+		return ! $has_insert;
+	}
+
+	/**
+	 * This method converts old forum hierarchy to new bbPress hierarchy.
+	 */
+	public function convert_forum_parents( $start = 1 ) {
+		$has_update = false;
+		$query      = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}",           '_bbp_old_forum_parent_id' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_forum_parent_id' );
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			$parent_id = $this->callback_forumid( $row->meta_value );
+			$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_parent = %d WHERE ID = %d LIMIT 1", $parent_id, $row->value_id ) );
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts old topic stickies to new bbPress stickies.
+	 *
+	 * @since 2.5.0 bbPress (r5170)
+	 */
+	public function convert_topic_stickies( $start = 1 ) {
+		$has_update = false;
+		$query      = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}",           '_bbp_old_sticky_status_id', 'sticky' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'sticky' );
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			bbp_stick_topic( $row->value_id );
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts old topic super stickies to new bbPress super stickies.
+	 *
+	 * @since 2.5.0 bbPress (r5170)
+	 */
+	public function convert_topic_super_stickies( $start = 1 ) {
+		$has_update = false;
+		$query      = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}",           '_bbp_old_sticky_status_id', 'super-sticky' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'super-sticky' );
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			$super = true;
+			bbp_stick_topic( $row->value_id, $super );
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts old closed topics to bbPress closed topics.
+	 *
+	 * @since 2.6.0 bbPress (r5425)
+	 */
+	public function convert_topic_closed_topics( $start = 1 ) {
+		$has_update = false;
+		$query      = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}",           '_bbp_old_closed_status_id', 'closed' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_closed_status_id', 'closed' );
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			bbp_close_topic( $row->value_id );
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts old reply_to post id to new bbPress reply_to post id.
+	 *
+	 * @since 2.4.0 bbPress (r5093)
+	 */
+	public function convert_reply_to_parents( $start = 1 ) {
+		$has_update = false;
+		$query      = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}",           '_bbp_old_reply_to_id' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_reply_to_id' );
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			$reply_to = $this->callback_reply_to( $row->meta_value );
+			$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->postmeta} SET meta_value = %s WHERE meta_key = %s AND post_id = %d LIMIT 1", $reply_to, '_bbp_reply_to', $row->value_id ) );
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts anonymous topics.
+	 *
+	 * @since 2.6.0 bbPress (r5538)
+	 */
+	public function convert_anonymous_topic_authors( $start = 1 ) {
+
+		$has_update = false;
+
+		if ( ! empty( $this->sync_table ) ) {
+			$query = $this->wpdb->prepare( "SELECT sync_table1.value_id AS topic_id, sync_table1.meta_value AS topic_is_anonymous, sync_table2.meta_value AS topic_author
+				FROM {$this->sync_table_name} AS sync_table1
+				INNER JOIN {$this->sync_table_name} AS sync_table2
+				ON ( sync_table1.value_id = sync_table2.value_id )
+				WHERE sync_table1.meta_value = %s
+				AND sync_table2.meta_key = %s
+				LIMIT {$start}, {$this->max_rows}", 'true', '_bbp_old_topic_author_name_id' );
+		} else {
+			$query = $this->wpdb->prepare( "SELECT wp_postmeta1.post_id AS topic_id, wp_postmeta1.meta_value AS topic_is_anonymous, wp_postmeta2.meta_value AS topic_author
+				FROM {$this->wpdb->postmeta} AS wp_postmeta1
+				INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
+				ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
+				WHERE wp_postmeta1.meta_value = %s
+				AND wp_postmeta2.meta_key = %s
+				LIMIT {$start}, {$this->max_rows}", 'true', '_bbp_old_topic_author_name_id' );
+		}
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			$anonymous_topic_author_id = 0;
+			$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_author = %d WHERE ID = %d LIMIT 1", $anonymous_topic_author_id, $row->topic_id ) );
+
+			add_post_meta( $row->topic_id, '_bbp_anonymous_name', $row->topic_author );
+
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method converts anonymous replies.
+	 *
+	 * @since 2.6.0 bbPress (r5538)
+	 */
+	public function convert_anonymous_reply_authors( $start = 1 ) {
+
+		$has_update = false;
+
+		if ( ! empty( $this->sync_table ) ) {
+			$query = $this->wpdb->prepare( "SELECT sync_table1.value_id AS reply_id, sync_table1.meta_value AS reply_is_anonymous, sync_table2.meta_value AS reply_author
+				FROM {$this->sync_table_name} AS sync_table1
+				INNER JOIN {$this->sync_table_name} AS sync_table2
+				ON ( sync_table1.value_id = sync_table2.value_id )
+				WHERE sync_table1.meta_value = %s
+				AND sync_table2.meta_key = %s
+				LIMIT {$start}, {$this->max_rows}", 'true', '_bbp_old_reply_author_name_id' );
+		} else {
+			$query = $this->wpdb->prepare( "SELECT wp_postmeta1.post_id AS reply_id, wp_postmeta1.meta_value AS reply_is_anonymous, wp_postmeta2.meta_value AS reply_author
+				FROM {$this->wpdb->postmeta} AS wp_postmeta1
+				INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
+				ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
+				WHERE wp_postmeta1.meta_value = %s
+				AND wp_postmeta2.meta_key = %s
+				LIMIT {$start}, {$this->max_rows}", 'true', '_bbp_old_reply_author_name_id' );
+		}
+
+		foreach ( $this->count_rows_by_results( $query ) as $row ) {
+			$anonymous_reply_author_id = 0;
+			$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_author = %d WHERE ID = %d LIMIT 1", $anonymous_reply_author_id, $row->reply_id ) );
+
+			add_post_meta( $row->reply_id, '_bbp_anonymous_name', $row->reply_author );
+
+			$has_update = true;
+		}
+
+		return ! $has_update;
+	}
+
+	/**
+	 * This method deletes data from the wp database.
+	 *
+	 * @since 2.6.0 bbPress (r6456)
+	 */
+	public function clean() {
+
+		// Defaults
+		$has_delete = false;
+
+		/** Delete topics/forums/posts ****************************************/
+
+		$esc_like = $this->wpdb->esc_like( '_bbp_' ) . '%';
+		$query    = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id FROM {$this->sync_table_name} INNER JOIN {$this->wpdb->posts} ON(value_id = ID) WHERE meta_key LIKE %s AND value_type = %s GROUP BY value_id ORDER BY value_id DESC LIMIT {$this->max_rows}", $esc_like, 'post' )
+			: $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key LIKE %s GROUP BY post_id ORDER BY post_id DESC LIMIT {$this->max_rows}", $esc_like );
+
+		$posts = $this->get_results( $query, ARRAY_A );
+
+		if ( isset( $posts[0] ) && ! empty( $posts[0]['value_id'] ) ) {
+			foreach ( (array) $posts as $value ) {
+				$deleted = wp_delete_post( $value['value_id'], true );
+
+				// Only flag if not empty or error
+				if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
+					$has_delete = true;
+				}
+			}
+		}
+
+		/** Delete users ******************************************************/
+
+		$query = ! empty( $this->sync_table )
+			? $this->wpdb->prepare( "SELECT value_id FROM {$this->sync_table_name} INNER JOIN {$this->wpdb->users} ON(value_id = ID) WHERE meta_key = %s AND value_type = %s LIMIT {$this->max_rows}", '_bbp_old_user_id', 'user' )
+			: $this->wpdb->prepare( "SELECT user_id AS value_id FROM {$this->wpdb->usermeta} WHERE meta_key = %s LIMIT {$this->max_rows}", '_bbp_old_user_id' );
+
+		$users = $this->get_results( $query, ARRAY_A );
+
+		if ( ! empty( $users ) ) {
+			foreach ( $users as $value ) {
+				$deleted = wp_delete_user( $value['value_id'] );
+
+				// Only flag if not empty or error
+				if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
+					$has_delete = true;
+				}
+			}
+		}
+
+		unset( $posts );
+		unset( $users );
+
+		return ! $has_delete;
+	}
+
+	/**
+	 * This method deletes passwords from the wp database.
+	 *
+	 * @param int Start row
+	 */
+	public function clean_passwords( $start = 1 ) {
+		$has_delete = false;
+		$query      = $this->wpdb->prepare( "SELECT user_id, meta_value FROM {$this->wpdb->usermeta} WHERE meta_key = %s LIMIT {$start}, {$this->max_rows}", '_bbp_password' );
+		$converted  = $this->get_results( $query, ARRAY_A );
+
+		if ( ! empty( $converted ) ) {
+			foreach ( $converted as $value ) {
+				if ( is_serialized( $value['meta_value'] ) ) {
+					$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = '' WHERE ID = %d", $value['user_id'] ) );
+				} else {
+					$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = %s WHERE ID = %d", $value['meta_value'], $value['user_id'] ) );
+					$this->query( $this->wpdb->prepare( "DELETE FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND user_id = %d", '_bbp_password', $value['user_id'] ) );
+				}
+			}
+			$has_delete = true;
+		}
+
+		return ! $has_delete;
+	}
+
+	/**
+	 * This method implements the authentication for the different forums.
+	 *
+	 * @param string Un-encoded password.
+	 */
+	abstract protected function authenticate_pass( $password, $hash );
+
+	/**
+	 * Info
+	 */
+	abstract protected function info();
+
+	/**
+	 * This method grabs appropriate fields from the table specified
+	 *
+	 * @param string The table name to grab fields from
+	 */
+	private function get_fields( $tablename = '' ) {
+		$retval      = array();
+		$field_array = $this->get_results( "DESCRIBE {$tablename}", ARRAY_A );
+
+		// Bail if no fields
+		if ( empty( $field_array ) ) {
+			return $retval;
+		}
+
+		// Add fields to array
+		foreach ( $field_array as $field ) {
+			if ( ! empty( $field['Field'] ) ) {
+				$retval[] = $field['Field'];
+			}
+		}
+
+		// Add social fields for users table
+		if ( $tablename === $this->wpdb->users ) {
+			$retval[] = 'role';
+			$retval[] = 'yim';
+			$retval[] = 'aim';
+			$retval[] = 'jabber';
+		}
+
+		return $retval;
+	}
+
+	/** Database Wrappers *****************************************************/
+
+	/**
+	 * Update the last query option and return results
+	 *
+	 * @param string $query
+	 * @param string $output
+	 */
+	private function get_row( $query = '' ) {
+		$this->update_query( $query );
+
+		return $this->wpdb->get_row( $query );
+	}
+
+	/**
+	 * Update the last query option and return results
+	 *
+	 * @param string $query
+	 * @param string $output
+	 */
+	private function get_results( $query = '', $output = OBJECT ) {
+		$this->update_query( $query );
+
+		return (array) $this->wpdb->get_results( $query, $output );
+	}
+
+	/**
+	 * Update the last query option and do a general query
+	 *
+	 * @param string $query
+	 */
+	private function query( $query = '' ) {
+		$this->update_query( $query );
+
+		return $this->wpdb->query( $query );
+	}
+
+	/**
+	 * Update the last query ran
+	 *
+	 * @since 2.6.0 bbPress (r6637)
+	 *
+	 * @param string $query The literal MySQL query
+	 * @return bool
+	 */
+	private function update_query( $query = '' ) {
+		return update_option( '_bbp_converter_query', $query );
+	}
+
+	/**
+	 * Update the number of rows in the current step
+	 *
+	 * @since 2.6.0 bbPress (r6637)
+	 *
+	 * @param string $query The literal MySQL query
+	 * @return bool
+	 */
+	private function count_rows_by_results( $query = '' ) {
+		$results = $this->get_results( $query );
+
+		update_option( '_bbp_converter_rows_in_step', count( $results ) );
+
+		return $results;
+	}
+
+	/**
+	 * Update the number of rows in the current step
+	 *
+	 * @since 2.6.0 bbPress (r6637)
+	 *
+	 * @param string $table_name The literal MySQL query
+	 * @return bool
+	 */
+	private function count_rows_by_table( $table_name = '' ) {
+		$count = (int) $this->opdb->get_var( "SELECT COUNT(*) FROM {$table_name}" );
+
+		return update_option( '_bbp_converter_rows_in_step', $count );
+	}
+
+	/** Callbacks *************************************************************/
+
+	/**
+	 * Run password through wp_hash_password()
+	 *
+	 * @param string $username
+	 * @param string $password
+	 */
+	public function callback_pass( $username, $password ) {
+		$user = $this->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->users} WHERE user_login = %s AND user_pass = '' LIMIT 1", $username ) );
+		if ( ! empty( $user ) ) {
+			$usermeta = $this->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND user_id = %d LIMIT 1", '_bbp_password', $user->ID ) );
+
+			if ( ! empty( $usermeta ) ) {
+				if ( $this->authenticate_pass( $password, $usermeta->meta_value ) ) {
+					$this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = %s WHERE ID = %d", wp_hash_password( $password ), $user->ID ) );
+					$this->query( $this->wpdb->prepare( "DELETE FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND user_id = %d", '_bbp_password', $user->ID ) );
+
+					// Clean the cache for this user since their password was
+					// upgraded from the old platform to the new.
+					clean_user_cache( $user->ID );
+				}
+			}
+		}
+	}
+
+	/**
+	 * A mini cache system to reduce database calls to forum ID's
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	private function callback_forumid( $field ) {
+		if ( ! isset( $this->map_forumid[ $field ] ) ) {
+			$row = ! empty( $this->sync_table )
+				? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_forum_id', $field ) )
+				: $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_forum_id', $field ) );
+
+			$this->map_forumid[ $field ] = ! is_null( $row )
+				? $row->value_id
+				: 0;
+		}
+
+		return $this->map_forumid[ $field ];
+	}
+
+	/**
+	 * A mini cache system to reduce database calls to topic ID's
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	private function callback_topicid( $field ) {
+		if ( ! isset( $this->map_topicid[ $field ] ) ) {
+			$row = ! empty( $this->sync_table )
+				? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_topic_id', $field ) )
+				: $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_topic_id', $field ) );
+
+			$this->map_topicid[ $field ] = ! is_null( $row )
+				? $row->value_id
+				: 0;
+		}
+
+		return $this->map_topicid[ $field ];
+	}
+
+	/**
+	 * A mini cache system to reduce database calls to reply_to post id.
+	 *
+	 * @since 2.4.0 bbPress (r5093)
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	private function callback_reply_to( $field ) {
+		if ( ! isset( $this->map_reply_to[ $field ] ) ) {
+			$row = ! empty( $this->sync_table )
+				? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_reply_id', $field ) )
+				: $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_reply_id', $field ) );
+
+			$this->map_reply_to[ $field ] = ! is_null( $row )
+				? $row->value_id
+				: 0;
+		}
+
+		return $this->map_reply_to[ $field ];
+	}
+
+	/**
+	 * A mini cache system to reduce database calls to user ID's
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	private function callback_userid( $field ) {
+		if ( ! isset( $this->map_userid[ $field ] ) ) {
+			$row = ! empty( $this->sync_table )
+				? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) )
+				: $this->get_row( $this->wpdb->prepare( "SELECT user_id AS value_id FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) );
+
+			if ( ! is_null( $row ) ) {
+				$this->map_userid[ $field ] = $row->value_id;
+			} else {
+				$this->map_userid[ $field ] = ( true === $this->convert_users )
+					? 0
+					: $field;
+			}
+		}
+
+		return $this->map_userid[ $field ];
+	}
+
+	/**
+	 * Check if the topic or reply author is anonymous
+	 *
+	 * @since 2.6.0 bbPress (r5544)
+	 *
+	 * @param  string $field
+	 * @return string
+	 */
+	private function callback_check_anonymous( $field ) {
+		$field = ( $this->callback_userid( $field ) == 0 )
+			? 'true'
+			: 'false';
+
+		return $field;
+	}
+
+	/**
+	 * A mini cache system to reduce database calls map topics ID's to forum ID's
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	private function callback_topicid_to_forumid( $field ) {
+		$topicid = $this->callback_topicid( $field );
+		if ( empty( $topicid ) ) {
+			$this->map_topicid_to_forumid[ $topicid ] = 0;
+		} elseif ( ! isset( $this->map_topicid_to_forumid[ $topicid ] ) ) {
+			$row = $this->get_row( $this->wpdb->prepare( "SELECT post_parent FROM {$this->wpdb->posts} WHERE ID = %d LIMIT 1", $topicid ) );
+
+			$this->map_topicid_to_forumid[ $topicid ] = ! is_null( $row )
+				? $row->post_parent
+				: 0;
+		}
+
+		return $this->map_topicid_to_forumid[ $topicid ];
+	}
+
+	protected function callback_slug( $field ) {
+		return sanitize_title( $field );
+	}
+
+	protected function callback_negative( $field ) {
+		return ( $field < 0 )
+			? 0
+			: $field;
+	}
+
+	protected function callback_html( $field ) {
+		require_once bbp_admin()->admin_dir . 'parser.php';
+
+		// Setup the BBCode parser
+		$bbcode = BBCode::getInstance();
+
+		// Pass BBCode properties to the parser
+		foreach ( $this->bbcode_parser_properties as $prop => $value ) {
+			$bbcode->{$prop} = $value;
+		}
+
+		return html_entity_decode( $bbcode->Parse( $field ) );
+	}
+
+	protected function callback_null( $field ) {
+		return is_null( $field )
+			? ''
+			: $field;
+	}
+
+	protected function callback_datetime( $field ) {
+		return is_numeric( $field )
+			? date( 'Y-m-d H:i:s', $field )
+			: date( 'Y-m-d H:i:s', strtotime( $field ) );
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-db.php
===================================================================
--- /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-db.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/classes/class-bbp-converter-db.php	(revision 7162)
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * bbPress Converter Database
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Converter_DB' ) && class_exists( 'wpdb' ) ) :
+/**
+ * bbPress Converter Database Access Abstraction Object
+ *
+ * @since 2.6.0 bbPress (r6784)
+ */
+class BBP_Converter_DB extends wpdb {
+
+	/**
+	 * Sets up the credentials used to connect to the database server, but does
+	 * not actually connect to the database on construct.
+	 *
+	 * @since 2.6.0 bbPress (r6784)
+	 *
+	 * @param string $dbuser     MySQL database user
+	 * @param string $dbpassword MySQL database password
+	 * @param string $dbname     MySQL database name
+	 * @param string $dbhost     MySQL database host
+	 */
+	public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
+		register_shutdown_function( array( $this, '__destruct' ) );
+
+		if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
+			$this->show_errors();
+		}
+
+		// Use ext/mysqli if it exists unless WP_USE_EXT_MYSQL is defined as true
+		if ( function_exists( 'mysqli_connect' ) ) {
+			$this->use_mysqli = true;
+
+			if ( defined( 'WP_USE_EXT_MYSQL' ) ) {
+				$this->use_mysqli = ! WP_USE_EXT_MYSQL;
+			}
+		}
+
+		// Setup credentials
+		$this->dbuser     = $dbuser;
+		$this->dbpassword = $dbpassword;
+		$this->dbname     = $dbname;
+		$this->dbhost     = $dbhost;
+
+		// Normally wpdb would try to connect here, but we don't want to do that
+		// until we are good and ready, so instead we do nothing.
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/admin/classes/class-bbp-converter.php
===================================================================
--- /tags/2.6.6/src/includes/admin/classes/class-bbp-converter.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/classes/class-bbp-converter.php	(revision 7162)
@@ -0,0 +1,893 @@
+<?php
+
+/**
+ * bbPress Converter
+ *
+ * Based on the hard work of Adam Ellis
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Converter' ) ) :
+/**
+ * Main BBP_Converter Class
+ */
+class BBP_Converter {
+
+	/**
+	 * @var int Number of rows
+	 */
+	public $max = 0;
+
+	/**
+	 * @var int Start
+	 */
+	public $start = 0;
+
+	/**
+	 * @var int Step in converter process
+	 */
+	public $step = 0;
+
+	/**
+	 * @var int Number of rows
+	 */
+	public $rows = 0;
+
+	/**
+	 * @var int Maximum number of converter steps
+	 */
+	public $max_steps = 17;
+
+	/**
+	 * @var int Number of rows in the current step
+	 */
+	public $rows_in_step = 0;
+
+	/**
+	 * @var int Percent complete of current step
+	 */
+	public $step_percentage = 0;
+
+	/**
+	 * @var int Percent complete of all step
+	 */
+	public $total_percentage = 0;
+
+	/**
+	 * @var int Name of source forum platform
+	 */
+	public $platform = '';
+
+	/**
+	 * @var BBP_Converter_Base Type of converter to use
+	 */
+	public $converter = null;
+
+	/**
+	 * @var string Path to included platforms
+	 */
+	public $converters_dir = '';
+
+	/**
+	 * @var array Map of steps to methods
+	 */
+	private $steps = array(
+		1  => 'sync_table',
+		2  => 'users',
+		3  => 'passwords',
+		4  => 'forums',
+		5  => 'forum_hierarchy',
+		6  => 'forum_subscriptions',
+		7  => 'topics',
+		8  => 'topics_authors',
+		9  => 'stickies',
+		10 => 'super_stickies',
+		11 => 'closed_topics',
+		12 => 'topic_tags',
+		13 => 'topic_subscriptions',
+		14 => 'topic_favorites',
+		15 => 'replies',
+		16 => 'reply_authors',
+		17 => 'reply_hierarchy'
+	);
+
+	/**
+	 * The main bbPress Converter loader
+	 *
+	 * @since 2.1.0 bbPress (r3813)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Admin globals
+	 *
+	 * @since 2.6.0 bbPress (r6598)
+	 */
+	public function setup_globals() {
+		$this->converters_dir = bbp_setup_admin()->admin_dir . 'converters/';
+	}
+
+	/**
+	 * Setup the default actions
+	 *
+	 * @since 2.1.0 bbPress (r3813)
+	 */
+	public function setup_actions() {
+
+		// Attach to the admin head with our ajax requests cycle and css
+		add_action( 'admin_head-tools_page_bbp-converter', array( $this, 'admin_head' ) );
+
+		// Attach to the admin ajax request to process cycles
+		add_action( 'wp_ajax_bbp_converter_process', array( $this, 'process_callback' ) );
+	}
+
+	/**
+	 * Admin scripts
+	 *
+	 * @since 2.1.0 bbPress (r3813)
+	 */
+	public function admin_head() {
+
+		// Enqueue scripts
+		wp_enqueue_script( 'bbp-converter' );
+
+		// Localize JS
+		wp_localize_script( 'bbp-converter', 'BBP_Converter', array(
+
+			// Nonce
+			'ajax_nonce' => wp_create_nonce( 'bbp_converter_process' ),
+
+			// UI State
+			'state' => array(
+				'delay'         => (int)  get_option( '_bbp_converter_delay_time', 2 ),
+				'started'       => (bool) get_option( '_bbp_converter_step',       0 ),
+				'running'       => false,
+				'status'        => false,
+				'step_percent'  => $this->step_percentage,
+				'total_percent' => $this->total_percentage
+			),
+
+			// Strings
+			'strings' => array(
+
+				// Button text
+				'button_start'        => esc_html__( 'Start',    'bbpress' ),
+				'button_continue'     => esc_html__( 'Continue', 'bbpress' ),
+
+				// Start button clicked
+				'start_start'         => esc_html__( 'Starting Import...',   'bbpress' ),
+				'start_continue'      => esc_html__( 'Continuing Import...', 'bbpress' ),
+
+				// Import
+				'import_complete'     => esc_html__( 'Import Finished.',            'bbpress' ),
+				'import_stopped_user' => esc_html__( 'Import Stopped (by User.)',   'bbpress' ),
+				'import_error_halt'   => esc_html__( 'Import Halted (Error.)',      'bbpress' ),
+				'import_error_db'     => esc_html__( 'Database Connection Failed.', 'bbpress' ),
+
+				// Status
+				'status_complete'     => esc_html__( 'Finished',              'bbpress' ),
+				'status_stopped'      => esc_html__( 'Stopped',               'bbpress' ),
+				'status_starting'     => esc_html__( 'Starting',              'bbpress' ),
+				'status_up_next'      => esc_html__( 'Doing step %s...',      'bbpress' ),
+				'status_counting'     => esc_html__( 'Next in %s seconds...', 'bbpress' )
+			)
+		) );
+	}
+
+	/**
+	 * Callback processor
+	 *
+	 * @since 2.1.0 bbPress (r3813)
+	 */
+	public function process_callback() {
+
+		// Ready the converter
+		$this->check_access();
+		$this->maybe_set_memory();
+		$this->maybe_restart();
+		$this->setup_options();
+		$this->maybe_update_options();
+
+		// Bail if no converter
+		if ( ! empty( $this->converter ) ) {
+			$this->do_steps();
+		}
+	}
+
+	/**
+	 * Wrap the converter output in HTML, so styling can be applied
+	 *
+	 * @since 2.1.0 bbPress (r4052)
+	 *
+	 * @param string $output
+	 */
+	private function converter_response( $output = '' ) {
+
+		// Sanitize output
+		$output = wp_kses_data( $output );
+
+		// Maybe prepend the step
+		if ( ! empty( $this->step ) ) {
+
+			// Include percentage
+			if ( ! empty( $this->rows_in_step ) ) {
+				$progress = sprintf( '<span class="step">%s.</span><span class="output">%s</span><span class="mini-step">%s</span>', $this->step, $output, $this->step_percentage . '%' );
+
+			// Don't include percentage
+			} else {
+				$progress = sprintf( '<span class="step">%s.</span><span class="output">%s</span>', $this->step, $output );
+			}
+
+		// Raw text
+		} else {
+			$progress = $output;
+		}
+
+		// Output
+		wp_send_json_success( array(
+			'query'         => get_option( '_bbp_converter_query', '' ),
+			'current_step'  => $this->step,
+			'final_step'    => $this->max_steps,
+			'rows_in_step'  => $this->rows_in_step,
+			'step_percent'  => $this->step_percentage,
+			'total_percent' => $this->total_percentage,
+			'progress'      => $progress
+		) );
+	}
+
+	/**
+	 * Attempt to increase memory and set other system settings
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function maybe_set_memory() {
+
+		// Filter args
+		$r = apply_filters( 'bbp_converter_php_ini_overrides', array(
+			'implicit_flush'     => '1',
+			'memory_limit'       => '256M',
+			'max_execution_time' => HOUR_IN_SECONDS * 6
+		) );
+
+		// Get disabled PHP functions (to avoid using them)
+		$disabled = explode( ',', @ini_get( 'disable_functions' ) );
+
+		// Maybe avoid terminating when the client goes away (if function is not disabled)
+		if ( ! in_array( 'ignore_user_abort', $disabled, true ) ) {
+			@ignore_user_abort( true );
+		}
+
+		// Maybe set memory & time limits, and flush style (if function is not disabled)
+		if ( ! in_array( 'ini_set', $disabled, true ) ) {
+			foreach ( $r as $key => $value ) {
+				@ini_set( $key, $value );
+			}
+		}
+	}
+
+	/**
+	 * Maybe restart the converter
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function maybe_restart() {
+
+		// Save step and count so that it can be restarted.
+		if ( ! get_option( '_bbp_converter_step' ) || ! empty( $_POST['_bbp_converter_restart'] ) ) {
+			$this->step             = 1;
+			$this->start            = 0;
+			$this->step_percentage  = 0;
+			$this->total_percentage = 0;
+			$this->rows_in_step     = 0;
+			$this->maybe_update_options();
+		}
+	}
+
+	/**
+	 * Maybe update options
+	 *
+	 * @since 2.6.0 bbPress (r6637)
+	 */
+	private function maybe_update_options() {
+
+		// Default options
+		$options = array(
+
+			// Step & Start
+			'_bbp_converter_step'         => $this->step,
+			'_bbp_converter_start'        => $this->start,
+			'_bbp_converter_rows_in_step' => $this->rows_in_step,
+
+			// Halt
+			'_bbp_converter_halt' => ! empty( $_POST['_bbp_converter_halt'] )
+				? (int) $_POST['_bbp_converter_halt']
+				: 0,
+
+			// Rows (bound between 1 and 5000)
+			'_bbp_converter_rows' => ! empty( $_POST['_bbp_converter_rows'] )
+				? min( max( (int) $_POST['_bbp_converter_rows'], 1 ), 5000 )
+				: 0,
+
+			// Platform
+			'_bbp_converter_platform' => ! empty( $_POST['_bbp_converter_platform' ] )
+				? sanitize_text_field( $_POST['_bbp_converter_platform' ] )
+				: '',
+
+			// Convert Users
+			'_bbp_converter_convert_users' => ! empty( $_POST['_bbp_converter_convert_users'] )
+				? (bool) $_POST['_bbp_converter_convert_users']
+				: false,
+
+			// DB User
+			'_bbp_converter_db_user' => ! empty( $_POST['_bbp_converter_db_user'] )
+				? sanitize_text_field( $_POST['_bbp_converter_db_user'] )
+				: '',
+
+			// DB Password
+			'_bbp_converter_db_pass' => ! empty( $_POST['_bbp_converter_db_pass'] )
+				? sanitize_text_field( $_POST['_bbp_converter_db_pass'] )
+				: '',
+
+			// DB Name
+			'_bbp_converter_db_name' => ! empty( $_POST['_bbp_converter_db_name'] )
+				? sanitize_text_field( $_POST['_bbp_converter_db_name'] )
+				: '',
+
+			// DB Server
+			'_bbp_converter_db_server' => ! empty( $_POST['_bbp_converter_db_server'] )
+				? sanitize_text_field( $_POST['_bbp_converter_db_server'] )
+				: '',
+
+			// DB Port
+			'_bbp_converter_db_port' => ! empty( $_POST['_bbp_converter_db_port'] )
+				? (int) sanitize_text_field( $_POST['_bbp_converter_db_port'] )
+				: '',
+
+			// DB Table Prefix
+			'_bbp_converter_db_prefix' => ! empty( $_POST['_bbp_converter_db_prefix'] )
+				? sanitize_text_field( $_POST['_bbp_converter_db_prefix'] )
+				: ''
+		);
+
+		// Update/delete options
+		foreach ( $options as $key => $value ) {
+			update_option( $key, $value );
+		}
+	}
+
+	/**
+	 * Setup converter options
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function setup_options() {
+
+		// Set starting point & rows
+		$this->step         = (int) get_option( '_bbp_converter_step',         1   );
+		$this->start        = (int) get_option( '_bbp_converter_start',        0   );
+		$this->rows         = (int) get_option( '_bbp_converter_rows',         100 );
+		$this->rows_in_step = (int) get_option( '_bbp_converter_rows_in_step', 0   );
+
+		// Set boundaries
+		$this->max          = ( $this->start + $this->rows ) - 1;
+
+		// Set platform
+		$this->platform     = get_option( '_bbp_converter_platform' );
+
+		// Total percentage
+		$this->total_percentage = round( ( $this->step / $this->max_steps ) * 100, 2 );
+
+		// Total mini steps
+		if ( $this->rows_in_step > 0 ) {
+			$total_mini_steps      = ceil( $this->rows_in_step / $this->rows );
+			$current_mini_step     = ceil( $this->start        / $this->rows );
+			$this->step_percentage = round( ( $current_mini_step / $total_mini_steps ) * 100, 2 );
+		} else {
+			$this->step_percentage = 0;
+		}
+
+		// Maybe include the appropriate converter.
+		if ( ! empty( $this->platform ) ) {
+			$this->converter = bbp_new_converter( $this->platform );
+		}
+	}
+
+	/**
+	 * Check that user can access the converter
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function check_access() {
+
+		// Bail if user cannot view import page
+		if ( ! current_user_can( 'bbp_tools_import_page' ) ) {
+			wp_die( '0' );
+		}
+
+		// Verify intent
+		check_ajax_referer( 'bbp_converter_process' );
+	}
+
+	/**
+	 * Reset the converter
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function reset() {
+		update_option( '_bbp_converter_step',         0  );
+		update_option( '_bbp_converter_start',        0  );
+		update_option( '_bbp_converter_rows_in_step', 0  );
+		update_option( '_bbp_converter_query',        '' );
+	}
+
+	/**
+	 * Bump the step and reset the start
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function bump_step() {
+
+		// Next step
+		$next_step = (int) ( $this->step + 1 );
+
+		// Don't let step go over max
+		$step = ( $next_step <= $this->max_steps )
+			? $next_step
+			: 0;
+
+		// Update step and start at 0
+		update_option( '_bbp_converter_step',         $step );
+		update_option( '_bbp_converter_start',        0     );
+		update_option( '_bbp_converter_rows_in_step', 0     );
+	}
+
+	/**
+	 * Bump the start within the current step
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function bump_start() {
+
+		// Set rows in step from option
+		$this->rows_in_step = get_option( '_bbp_converter_rows_in_step', 0 );
+
+		// Get rows to start from
+		$start = (int) ( $this->start + $this->rows );
+
+		// Enforce maximum if exists
+		if ( $this->rows_in_step > 0 ) {
+
+			// Start cannot be larger than total rows
+			if ( $start > $this->rows_in_step ) {
+				$start = $this->rows_in_step;
+			}
+
+			// Max can't be greater than total rows
+			if ( $this->max > $this->rows_in_step ) {
+				$this->max = $this->rows_in_step;
+			}
+		}
+
+		// Update the start option
+		update_option( '_bbp_converter_start', $start );
+	}
+
+	/**
+	 * Do the converter step
+	 *
+	 * @since 2.6.0 bbPress (r6460)
+	 */
+	private function do_steps() {
+
+		// Step exists in map, and method exists
+		if ( isset( $this->steps[ $this->step ] ) && method_exists( $this, "step_{$this->steps[ $this->step ]}" ) ) {
+			return call_user_func( array( $this, "step_{$this->steps[ $this->step ]}" ) );
+		}
+
+		// Done!
+		$this->step_done();
+	}
+
+	/** Steps *****************************************************************/
+
+	/**
+	 * Maybe clean the sync table
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_sync_table() {
+		if ( true === $this->converter->clean ) {
+			if ( $this->converter->clean() ) {
+				$this->bump_step();
+				$this->sync_table( true );
+
+				empty( $this->start )
+					? $this->converter_response( esc_html__( 'Readying sync-table', 'bbpress' ) )
+					: $this->converter_response( esc_html__( 'Sync-table ready',    'bbpress' ) );
+			} else {
+				$this->bump_start();
+				$this->converter_response( sprintf( esc_html__( 'Deleting previously converted data (%1$s through %2$s)', 'bbpress' ), $this->start, $this->max ) );
+			}
+
+			$this->converter->clean = false;
+		} else {
+			$this->bump_step();
+			$this->sync_table( false );
+			$this->converter_response( esc_html__( 'Skipping sync-table clean-up', 'bbpress' ) );
+		}
+	}
+
+	/**
+	 * Maybe convert users
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_users() {
+		if ( true === $this->converter->convert_users ) {
+			if ( $this->converter->convert_users( $this->start ) ) {
+				$this->bump_step();
+
+				empty( $this->start )
+					? $this->converter_response( esc_html__( 'No users to import', 'bbpress' ) )
+					: $this->converter_response( esc_html__( 'All users imported', 'bbpress' ) );
+			} else {
+				$this->bump_start();
+				$this->converter_response( sprintf( esc_html__( 'Converting users (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+			}
+		} else {
+			$this->bump_step();
+			$this->converter_response( esc_html__( 'Skipping user clean-up', 'bbpress' ) );
+		}
+	}
+
+	/**
+	 * Maybe clean up passwords
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_passwords() {
+		if ( true === $this->converter->convert_users ) {
+			if ( $this->converter->clean_passwords( $this->start ) ) {
+				$this->bump_step();
+
+				empty( $this->start )
+					? $this->converter_response( esc_html__( 'No passwords to clear', 'bbpress' ) )
+					: $this->converter_response( esc_html__( 'All passwords cleared', 'bbpress' ) );
+			} else {
+				$this->bump_start();
+				$this->converter_response( sprintf( esc_html__( 'Delete default WordPress user passwords (%1$s through %2$s)', 'bbpress' ), $this->start, $this->max ) );
+			}
+		} else {
+			$this->bump_step();
+			$this->converter_response( esc_html__( 'Skipping password clean-up', 'bbpress' ) );
+		}
+	}
+
+	/**
+	 * Maybe convert forums
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_forums() {
+		if ( $this->converter->convert_forums( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No forums to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All forums imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting forums (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe walk the forum hierarchy
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_forum_hierarchy() {
+		if ( $this->converter->convert_forum_parents( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No forum parents to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All forum parents imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Calculating forum hierarchy (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert forum subscriptions
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_forum_subscriptions() {
+		if ( $this->converter->convert_forum_subscriptions( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No forum subscriptions to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All forum subscriptions imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting forum subscriptions (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert topics
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_topics() {
+		if ( $this->converter->convert_topics( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No topics to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All topics imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting topics (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert topic authors (anonymous)
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_topics_authors() {
+		if ( $this->converter->convert_anonymous_topic_authors( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No anonymous topic authors to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All anonymous topic authors imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting anonymous topic authors (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert sticky topics (not super stickies)
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_stickies() {
+		if ( $this->converter->convert_topic_stickies( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No stickies to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All stickies imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Calculating topic stickies (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert super-sticky topics (not per-forum)
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_super_stickies() {
+		if ( $this->converter->convert_topic_super_stickies( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No super stickies to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All super stickies imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Calculating topic super stickies (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe close converted topics
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_closed_topics() {
+		if ( $this->converter->convert_topic_closed_topics( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No closed topics to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All closed topics imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Calculating closed topics (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert topic tags
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_topic_tags() {
+		if ( $this->converter->convert_tags( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No topic tags to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All topic tags imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting topic tags (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert topic subscriptions
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_topic_subscriptions() {
+		if ( $this->converter->convert_topic_subscriptions( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No topic subscriptions to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All topic subscriptions imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting topic subscriptions (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert topic favorites
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_topic_favorites() {
+		if ( $this->converter->convert_favorites( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No favorites to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All favorites imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting favorites (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert replies
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_replies() {
+		if ( $this->converter->convert_replies( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No replies to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All replies imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting replies (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert reply authors (anonymous)
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_reply_authors() {
+		if ( $this->converter->convert_anonymous_reply_authors( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No anonymous reply authors to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All anonymous reply authors imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Converting anonymous reply authors (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Maybe convert the threaded reply hierarchy
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_reply_hierarchy() {
+		if ( $this->converter->convert_reply_to_parents( $this->start ) ) {
+			$this->bump_step();
+
+			empty( $this->start )
+				? $this->converter_response( esc_html__( 'No threaded replies to import', 'bbpress' ) )
+				: $this->converter_response( esc_html__( 'All threaded replies imported', 'bbpress' ) );
+		} else {
+			$this->bump_start();
+			$this->converter_response( sprintf( esc_html__( 'Calculating threaded replies parents (%1$s through %2$s of %3$s)', 'bbpress' ), $this->start, $this->max, $this->rows_in_step ) );
+		}
+	}
+
+	/**
+	 * Done!
+	 *
+	 * @since 2.6.0 bbPress (r6513)
+	 */
+	private function step_done() {
+		$this->reset();
+		$this->converter_response( esc_html__( 'Import Finished', 'bbpress' ) );
+	}
+
+	/** Helper Table **********************************************************/
+
+	/**
+	 * Create Tables for fast syncing
+	 *
+	 * @since 2.1.0 bbPress (r3813)
+	 */
+	public static function sync_table( $drop = false ) {
+
+		// Setup DB
+		$bbp_db       = bbp_db();
+		$table_name   = $bbp_db->prefix . 'bbp_converter_translator';
+		$table_exists = $bbp_db->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
+
+		// Maybe drop the sync table
+		if ( ( true === $drop ) && ( true === $table_exists ) ) {
+			$bbp_db->query( "DROP TABLE {$table_name}" );
+		}
+
+		// Maybe include the upgrade functions, for dbDelta()
+		if ( ! function_exists( 'dbDelta' ) ) {
+			require_once ABSPATH . '/wp-admin/includes/upgrade.php';
+		}
+
+		// Defaults
+		$sql              = array();
+		$charset_collate  = '';
+
+		// https://bbpress.trac.wordpress.org/ticket/3145
+		$max_index_length = 75;
+
+		// Maybe override the character set
+		if ( ! empty( $bbp_db->charset ) ) {
+			$charset_collate .= "DEFAULT CHARACTER SET {$bbp_db->charset}";
+		}
+
+		// Maybe override the collation
+		if ( ! empty( $bbp_db->collate ) ) {
+			$charset_collate .= " COLLATE {$bbp_db->collate}";
+		}
+
+		/** Translator ********************************************************/
+
+		$sql[] = "CREATE TABLE {$table_name} (
+					meta_id mediumint(8) unsigned not null auto_increment,
+					value_type varchar(25) null,
+					value_id bigint(20) unsigned not null default '0',
+					meta_key varchar({$max_index_length}) null,
+					meta_value varchar({$max_index_length}) null,
+				PRIMARY KEY (meta_id),
+					KEY value_id (value_id),
+					KEY meta_join (meta_key({$max_index_length}), meta_value({$max_index_length}))
+				) {$charset_collate}";
+
+		dbDelta( $sql );
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/admin/classes/class-bbp-topic-replies-list-table.php
===================================================================
--- /tags/2.6.6/src/includes/admin/classes/class-bbp-topic-replies-list-table.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/classes/class-bbp-topic-replies-list-table.php	(revision 7162)
@@ -0,0 +1,346 @@
+<?php
+
+/**
+ * Topic Replies List Table class.
+ *
+ * @package    bbPress
+ * @subpackage Administration
+ * @since      2.6.0
+ * @access     private
+ *
+ * @see WP_Posts_List_Table
+ */
+
+// Include the main list table class if it's not included yet
+if ( ! class_exists( 'WP_List_Table' ) ) {
+	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
+}
+
+if ( class_exists( 'WP_List_Table' ) ) :
+/**
+ * Topic replies list table
+ *
+ * This list table is responsible for showing the replies to a topic in a
+ * meta-box, similar to comments in posts and pages.
+ *
+ * @since 2.6.0 bbPress (r5886)
+ */
+class BBP_Topic_Replies_List_Table extends WP_List_Table {
+
+	/**
+	 * The main constructor method
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function __construct( $args = array() ) {
+
+		// Parse arguments
+		$args = bbp_parse_args( $args, array(
+			'singular' => 'reply',
+			'plural'   => 'replies',
+			'ajax'     => false
+		), 'topic_replies_list_table' );
+
+		// Construct the list table
+		parent::__construct( $args );	}
+
+	/**
+	 * Setup the list-table columns
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 *
+	 * @see WP_List_Table::::single_row_columns()
+	 *
+	 * @return array An associative array containing column information
+	 */
+	public function get_columns() {
+		return array(
+			//'cb'                 => '<input type="checkbox" />',
+			'bbp_topic_reply_author' => esc_html__( 'Author',  'bbpress' ),
+			'bbp_reply_content'      => esc_html__( 'Content', 'bbpress' ),
+			'bbp_reply_created'      => esc_html__( 'Replied', 'bbpress' ),
+		);
+	}
+
+	/**
+	 * Allow `bbp_reply_created` to be sortable
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 *
+	 * @return array An associative array containing the `bbp_reply_created` column
+	 */
+	public function get_sortable_columns() {
+		return array(
+			'bbp_reply_created' => array( 'bbp_reply_created', false )
+		);
+	}
+
+	/**
+	 * Setup the bulk actions
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 *
+	 * @return array An associative array containing all the bulk actions
+	 */
+	public function get_bulk_actions() {
+		return array();
+
+		// @todo cap checks
+		return array(
+			'unapprove' => esc_html__( 'Unapprove', 'bbpress' ),
+			'spam'      => esc_html__( 'Spam',      'bbpress' ),
+			'trash'     => esc_html__( 'Trash',     'bbpress' )
+		);
+	}
+
+	/**
+	 * Output the check-box column for bulk actions (if we implement them)
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function column_cb( $item = '' ) {
+		return sprintf(
+			'<input type="checkbox" name="%1$s[]" value="%2$s" />',
+			$this->_args['singular'],
+			$item->ID
+		);
+	}
+
+	/**
+	 * Output the contents of the `bbp_topic_reply_author` column
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function column_bbp_topic_reply_author( $item = '' ) {
+		bbp_reply_author_avatar( $item->ID, 50 );
+		bbp_reply_author_display_name( $item->ID );
+		echo '<br>';
+		bbp_reply_author_email( $item->ID );
+		echo '<br>';
+		bbp_author_ip( array( 'post_id' => $item->ID ) );
+	}
+
+	/**
+	 * Output the contents of the `bbp_reply_created` column
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function column_bbp_reply_created( $item = '' ) {
+		return sprintf( '%1$s <br /> %2$s',
+			esc_attr( get_the_date( '', $item ) ),
+			esc_attr( get_the_time( '', $item ) )
+		);
+	}
+
+	/**
+	 * Output the contents of the `bbp_reply_content` column
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function column_bbp_reply_content( $item = '' ) {
+
+		// Define actions array
+		$actions = array(
+			'view' => '<a href="' . bbp_get_reply_url( $item->ID )  . '">' . esc_html__( 'View', 'bbpress' ) . '</a>'
+		);
+
+		// Prepend `edit` link
+		if ( current_user_can( 'edit_reply', $item->ID ) ) {
+			$actions['edit'] = '<a href="' . get_edit_post_link( $item->ID ) . '">' . esc_html__( 'Edit', 'bbpress' ) . '</a>';
+			$actions         = array_reverse( $actions );
+		}
+
+		// Filter the reply content
+		$reply_content = apply_filters( 'bbp_get_reply_content', $item->post_content, $item->ID );
+		$reply_actions = $this->row_actions( $actions );
+
+		// Return content & actions
+		return $reply_content . $reply_actions;
+	}
+
+	/**
+	 * Handle bulk action requests
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function process_bulk_action() {
+		switch ( $this->current_action() ) {
+			case 'trash' :
+				break;
+			case 'unapprove' :
+				break;
+			case 'spam' :
+				break;
+		}
+	}
+
+	/**
+	 * Prepare the list-table items for display
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function prepare_items( $topic_id = 0 ) {
+
+		// Sanitize the topic ID
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Set column headers
+		$this->_column_headers = array(
+			$this->get_columns(),
+			array(),
+			$this->get_sortable_columns()
+		);
+
+		// Handle bulk actions
+		$this->process_bulk_action();
+
+		// Query parameters
+		$per_page     = 5;
+		$current_page = $this->get_pagenum();
+		$orderby      = ! empty( $_REQUEST['orderby'] ) ? sanitize_key( $_REQUEST['orderby'] ) : 'date';
+		$order        = ! empty( $_REQUEST['order']   ) ? sanitize_key( $_REQUEST['order']   ) : 'asc';
+		$statuses     = bbp_get_public_reply_statuses();
+
+		// Maybe add private statuses to query
+		if ( current_user_can( 'edit_others_replies' ) ) {
+
+			// Default view=all statuses
+			$statuses = array_keys( bbp_get_topic_statuses() );
+
+			// Add support for private status
+			if ( current_user_can( 'read_private_replies' ) ) {
+				$statuses[] = bbp_get_private_status_id();
+			}
+		}
+
+		// Query for replies
+		$reply_query  = new WP_Query( array(
+			'post_type'           => bbp_get_reply_post_type(),
+			'post_status'         => $statuses,
+			'post_parent'         => $topic_id,
+			'posts_per_page'      => $per_page,
+			'paged'               => $current_page,
+			'orderby'             => $orderby,
+			'order'               => ucwords( $order ),
+			'hierarchical'        => false,
+			'ignore_sticky_posts' => true
+		) );
+
+		// Get the total number of replies, for pagination
+		$total_items = bbp_get_topic_reply_count( $topic_id );
+
+		// Set list table items to queried posts
+		$this->items = $reply_query->posts;
+
+		// Set the pagination arguments
+		$this->set_pagination_args( array(
+			'total_items' => $total_items,
+			'per_page'    => $per_page,
+			'total_pages' => ceil( $total_items / $per_page )
+		) );
+	}
+
+	/**
+	 * Message to be displayed when there are no items
+	 *
+	 * @since 2.6.0 bbPress (r5930)
+	 */
+	public function no_items() {
+		esc_html_e( 'No replies to this topic.', 'bbpress' );
+	}
+
+	/**
+	 * Display the list table
+	 *
+	 * This custom method is necessary because the one in `WP_List_Table` comes
+	 * with a nonce and check that we do not need.
+	 *
+	 * @since 2.6.0 bbPress (r5930)
+	 */
+	public function display() {
+
+		// Top
+		$this->display_tablenav( 'top' ); ?>
+
+		<table id="bbp-reply-list" class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
+			<thead>
+				<tr>
+					<?php $this->print_column_headers(); ?>
+				</tr>
+			</thead>
+
+			<tbody id="the-list" data-wp-lists='list:<?php echo $this->_args['singular']; ?>'>
+				<?php $this->display_rows_or_placeholder(); ?>
+			</tbody>
+
+			<tfoot>
+				<tr>
+					<?php $this->print_column_headers( false ); ?>
+				</tr>
+			</tfoot>
+		</table>
+
+		<?php
+
+		// Bottom
+		$this->display_tablenav( 'bottom' );
+	}
+
+	/**
+	 * Generate the table navigation above or below the table
+	 *
+	 * This custom method is necessary because the one in `WP_List_Table` comes
+	 * with a nonce and check that we do not need.
+	 *
+	 * @since 2.6.0 bbPress (r5930)
+	 *
+	 * @param string $which
+	 */
+	protected function display_tablenav( $which = '' ) {
+		?>
+
+		<div class="tablenav <?php echo esc_attr( $which ); ?>">
+			<?php
+				$this->extra_tablenav( $which );
+				$this->pagination( $which );
+			?>
+			<br class="clear" />
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Generates content for a single row of the table
+	 *
+	 * @since 2.6.0
+	 * @access public
+	 *
+	 * @param object $item The current item
+	 */
+	public function single_row( $item ) {
+
+		// Author
+		$classes = 'author-' . ( get_current_user_id() == $item->post_author ? 'self' : 'other' );
+
+		// Locked
+		if ( wp_check_post_lock( $item->ID ) ) {
+			$classes .= ' wp-locked';
+		}
+
+		// Hierarchy
+		if ( ! empty( $item->post_parent ) ) {
+		    $count    = count( get_post_ancestors( $item->ID ) );
+		    $classes .= ' level-'. $count;
+		} else {
+		    $classes .= ' level-0';
+		} ?>
+
+		<tr id="post-<?php echo esc_attr( $item->ID ); ?>" class="<?php echo implode( ' ', get_post_class( $classes, $item->ID ) ); ?>">
+			<?php $this->single_row_columns( $item ); ?>
+		</tr>
+
+		<?php
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/admin/common.php
===================================================================
--- /tags/2.6.6/src/includes/admin/common.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/common.php	(revision 7162)
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * bbPress Admin Functions
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Admin Menus ***************************************************************/
+
+/**
+ * Add a separator to the WordPress admin menus
+ *
+ * @since 2.0.0 bbPress (r2957)
+ */
+function bbp_admin_separator() {
+
+	// Caps necessary where a separator is necessary
+	$caps = array(
+		'bbp_forums_admin',
+		'bbp_topics_admin',
+		'bbp_replies_admin',
+	);
+
+	// Loop through caps, and look for a reason to show the separator
+	foreach ( $caps as $cap ) {
+		if ( current_user_can( $cap ) ) {
+			bbp_admin()->show_separator = true;
+			break;
+		}
+	}
+
+	// Bail if no separator
+	if ( false === bbp_admin()->show_separator ) {
+		return;
+	}
+
+	global $menu;
+
+	$menu[] = array( '', 'read', 'separator-bbpress', '', 'wp-menu-separator bbpress' );
+}
+
+/**
+ * Tell WordPress we have a custom menu order
+ *
+ * @since 2.0.0 bbPress (r2957)
+ *
+ * @param bool $menu_order Menu order
+ * @return mixed True if separator, false if not
+ */
+function bbp_admin_custom_menu_order( $menu_order = false ) {
+	if ( false === bbp_admin()->show_separator ) {
+		return $menu_order;
+	}
+
+	return true;
+}
+
+/**
+ * Move our custom separator above our custom post types
+ *
+ * @since 2.0.0 bbPress (r2957)
+ *
+ * @param array $menu_order Menu Order
+ * @return array Modified menu order
+ */
+function bbp_admin_menu_order( $menu_order ) {
+
+	// Bail if user cannot see any top level bbPress menus
+	if ( empty( $menu_order ) || ( false === bbp_admin()->show_separator ) ) {
+		return $menu_order;
+	}
+
+	// Initialize our custom order array
+	$bbp_menu_order = array();
+
+	// Menu values
+	$second_sep   = 'separator2';
+	$custom_menus = array(
+		'separator-bbpress',                               // Separator
+		'edit.php?post_type=' . bbp_get_forum_post_type(), // Forums
+		'edit.php?post_type=' . bbp_get_topic_post_type(), // Topics
+		'edit.php?post_type=' . bbp_get_reply_post_type()  // Replies
+	);
+
+	// Loop through menu order and do some rearranging
+	foreach ( $menu_order as $item ) {
+
+		// Position bbPress menus above appearance
+		if ( $second_sep == $item ) {
+
+			// Add our custom menus
+			foreach ( $custom_menus as $custom_menu ) {
+				if ( array_search( $custom_menu, $menu_order ) ) {
+					$bbp_menu_order[] = $custom_menu;
+				}
+			}
+
+			// Add the appearance separator
+			$bbp_menu_order[] = $second_sep;
+
+		// Skip our menu items
+		} elseif ( ! in_array( $item, $custom_menus, true ) ) {
+			$bbp_menu_order[] = $item;
+		}
+	}
+
+	// Return our custom order
+	return $bbp_menu_order;
+}
+
+/**
+ * Sanitize permalink slugs when saving the settings page.
+ *
+ * @since 2.6.0 bbPress (r5364)
+ *
+ * @param string $slug
+ * @return string
+ */
+function bbp_sanitize_slug( $slug = '' ) {
+
+	// Don't allow multiple slashes in a row
+	$value = preg_replace( '#/+#', '/', str_replace( '#', '', $slug ) );
+
+	// Strip out unsafe or unusable chars
+	$value = esc_url_raw( $value );
+
+	// esc_url_raw() adds a scheme via esc_url(), so let's remove it
+	$value = str_replace( 'http://', '', $value );
+
+	// Trim off first and last slashes.
+	//
+	// We already prevent double slashing elsewhere, but let's prevent
+	// accidental poisoning of options values where we can.
+	$value = ltrim( $value, '/' );
+	$value = rtrim( $value, '/' );
+
+	// Filter & return
+	return apply_filters( 'bbp_sanitize_slug', $value, $slug );
+}
+
+/**
+ * Uninstall all bbPress options and capabilities from a specific site.
+ *
+ * @since 2.1.0 bbPress (r3765)
+ *
+ * @param int $site_id
+ */
+function bbp_do_uninstall( $site_id = 0 ) {
+	if ( empty( $site_id ) ) {
+		$site_id = get_current_blog_id();
+	}
+
+	bbp_switch_to_site( $site_id );
+	bbp_delete_options();
+	bbp_remove_roles();
+	bbp_remove_caps();
+	flush_rewrite_rules();
+	bbp_restore_current_site();
+}
+
+/**
+ * This tells WP to highlight the Tools > Forums menu item,
+ * regardless of which actual bbPress Tools screen we are on.
+ *
+ * The conditional prevents the override when the user is viewing settings or
+ * any third-party plugins.
+ *
+ * @since 2.1.0 bbPress (r3888)
+ *
+ * @global string $plugin_page
+ * @global array $submenu_file
+ */
+function bbp_tools_modify_menu_highlight() {
+	global $plugin_page, $submenu_file;
+
+	// This tweaks the Tools subnav menu to only show one bbPress menu item
+	if ( ! in_array( $plugin_page, array( 'bbp-settings' ), true ) ) {
+		$submenu_file = 'bbp-repair';
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/AEF.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/AEF.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/AEF.php	(revision 7162)
@@ -0,0 +1,663 @@
+<?php
+
+/**
+ * bbPress AEF Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of AEF Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5139)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/aef
+ */
+class AEF extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'fid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'par_board_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'ntopic',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'nposts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'ntopic',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'nposts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'fname',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'fname',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Unlocked = 1 or Locked = 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'status',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'tid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'n_posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'n_posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 't_bid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: We join the 'posts' table because 'topics' table does not include author id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'posts' table because 'topics' table does not include author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'topics' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 't_bid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 't_sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		// Note: We join the 'posts' table because 'topics' table does not include topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.pid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open = 1 or Closed = 0, AEF v1.0.9)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 't_status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * AEF v1.0.9 Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'pid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_fid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ptime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'AEF'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'www',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'r_time',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'yim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_yim'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_msn'
+		);
+
+		// Store Gmail (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'gmail',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_gmail'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'sig',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_location'
+		);
+
+		// Store PrivateText (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'users_text',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_aef_user_private_text'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum status from AEF v1.0.9 numerics to WordPress's strings.
+	 *
+	 * @param int $status AEF v1.0.9 numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1 :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from AEF v1.0.9 numerics to WordPress's strings.
+	 *
+	 * @param int $status AEF v1.0.9 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from AEF 1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status AEF 1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // AEF Sticky 't_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // AEF normal topic 't_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count AEF v1.0.9 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Drupal7.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Drupal7.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Drupal7.php	(revision 7162)
@@ -0,0 +1,662 @@
+<?php
+
+/**
+ * bbPress Drupal7 Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Drupal v7.x Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5138)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/drupal
+ */
+class Drupal7 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'taxonomy_term_data',
+			'from_fieldname' => 'tid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_hierarchy',
+			'from_fieldname'  => 'parent',
+			'join_tablename'  => 'taxonomy_term_data',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid)',
+			'from_expression' => 'LEFT JOIN taxonomy_vocabulary AS taxonomy_vocabulary USING (vid) WHERE module = "forum"',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'taxonomy_term_data',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_data',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_data',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'taxonomy_term_data',
+			'from_fieldname' => 'weight',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_index',
+			'from_fieldname' => 'nid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'comment_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'comment_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: We join the 'node' table because 'forum_index' table does not include author id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'forum_index',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON node.nid = forum_index.nid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => '_bbp_old_topic_author_name_id',
+			'default'      => 'Anonymous'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'forum_index',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON node.nid = forum_index.nid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic content.
+		// Note: We join the 'field_data_body' table because 'node' or 'forum_index' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'field_data_body',
+			'from_fieldname'  => 'body_value',
+			'join_tablename'  => 'node',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON field_data_body.revision_id = node.vid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_index',
+			'from_fieldname' => 'title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Publish or Unpublished, Drupal v7.x publish = 1, pending = 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'status',
+			'join_tablename'  => 'forum_index',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON node.nid = forum_index.nid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'created',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'created',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'last_comment_timestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'last_comment_timestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_index',
+			'from_fieldname'  => 'last_comment_timestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Drupal v7.x Comments Enabled no = 0, closed = 1 & open = 2)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'comment',
+			'join_tablename'  => 'forum_index',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON node.nid = forum_index.nid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'field_data_field_tags',
+			'from_fieldname'  => 'entity_id',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'field_data_field_tags',
+			'from_fieldname'  => 'field_tags_tid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term name.
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_data',
+			'from_fieldname'  => 'name',
+			'join_tablename'  => 'field_data_field_tags',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON field_tags_tid = taxonomy_term_data.tid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_data',
+			'from_fieldname'  => 'name',
+			'join_tablename'  => 'field_data_field_tags',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON field_tags_tid = taxonomy_term_data.tid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Term description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'taxonomy_term_data',
+			'from_fieldname'  => 'description',
+			'join_tablename'  => 'field_data_field_tags',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON field_tags_tid = taxonomy_term_data.tid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'description'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'comment',
+			'from_fieldname' => 'cid',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'nid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'nid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply parent reply id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'pid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_to_id'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'comment',
+			'from_fieldname' => 'hostname',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'uid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply status (Publish or Unpublished, Drupal v7.x publish = 1, pending = 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'status',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename' => 'comment',
+			'from_fieldname' => 'name',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous  (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'uid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		// Note: We join the 'field_data_comment_body' table because 'comment' table does not include reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'field_data_comment_body',
+			'from_fieldname'  => 'comment_body_value',
+			'join_tablename'  => 'comment',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON field_data_comment_body.entity_id = comment.cid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'nid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'created',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'created',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'changed',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'comment',
+			'from_fieldname'  => 'changed',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		// Don't import user uid = 0, this is Drupal 7's guest user
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'uid',
+			'from_expression' => 'WHERE uid != 0',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'pass',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password'
+//			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'pass',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'Drupal7'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'mail',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'created',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'signature',
+			'to_fieldname'    => '_bbp_drupal7_user_sig',
+			'to_type'         => 'user',
+			'callback_method' => 'callback_html'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the post status from Drupal v7.x numerics to WordPress's
+	 * strings.
+	 *
+	 * @param int $status Drupal v7.x numeric post status
+	 * @return string WordPress safe
+	 */
+	public function callback_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'pending'; // bbp_get_pending_status_id()
+				break;
+
+			case 1  :
+			default :
+				$status = 'publish'; // bbp_get_public_status_id()
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from Drupal v7.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Drupal v7.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 2 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 2  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from Drupal v7.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Drupal v7.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky'; // Drupal Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal'; // Drupal Normal Topic 'sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count Drupal v7.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Example.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Example.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Example.php	(revision 7162)
@@ -0,0 +1,711 @@
+<?php
+
+/**
+ * bbPress Example Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Example converter base impoprter template for bbPress
+ *
+ * @since 2.3.0 bbPress (r4689)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/custom-import
+ */
+class Example extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Setup table joins for the forum section at the base of this section
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums_table',
+			'from_fieldname' => 'the_topic_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums_table',
+			'from_fieldname' => 'the_reply_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums_table',
+			'from_fieldname' => 'the_total_topic_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums_table',
+			'from_fieldname' => 'the_total_reply_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_title',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_slug',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_order',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_type',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Unlocked = 0 or Locked = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_table',
+			'from_fieldname'  => 'the_forum_status',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_date',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_date_gmt',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_modified',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_modified_gmt',
+			'default' => date('Y-m-d H:i:s')
+		);
+
+		// Setup the table joins for the forum section
+		$this->field_map[] = array(
+			'from_tablename'  => 'groups_table',
+			'from_fieldname'  => 'forum_id',
+			'join_tablename'  => 'forums_table',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING groups_table.forum_id = forums_table.forum_id',
+		//	'from_expression' => 'WHERE forums_table.forum_id != 1',
+			'to_type'         => 'forum'
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions_table',
+			'from_fieldname'  => 'the_forum_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions_table',
+			'from_fieldname'  => 'the_user_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Setup table joins for the topic section at the base of this section
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_reply_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_total_topic_reply_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_parent_forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_author_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_author_ip_address',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_content',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_slug',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_parent_forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_sticky_status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_creation_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_creation_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_modified_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_modified_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_modified_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Setup any table joins needed for the topic section
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_topic_id',
+			'join_tablename'  => 'topics_table',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING replies_table.the_topic_id = topics_table.the_topic_id',
+			'from_expression' => 'WHERE forums_table.the_topic_id = 0',
+			'to_type'         => 'topic'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Setup table joins for the tag section at the base of this section
+		// Setup any table joins needed for the tags section
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag_table',
+			'from_fieldname'  => 'the_topic_id',
+			'join_tablename'  => 'tagcontent_table',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING tagcontent_table.tag_id = tags_table.tag_id',
+			'from_expression' => 'WHERE tagcontent_table.tag_id = tag_table.tag_id',
+			'to_type'         => 'tags'
+		);
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagcontent_table',
+			'from_fieldname'  => 'contentid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagcontent_table',
+			'from_fieldname'  => 'tagid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag_table',
+			'from_fieldname'  => 'tagtext',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag_table',
+			'from_fieldname'  => 'tagslug',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Term description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag_table',
+			'from_fieldname'  => 'tagdescription',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'description'
+		);
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topic_subscriptions_table',
+			'from_fieldname'  => 'the_topic_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topic_subscriptions_table',
+			'from_fieldname'  => 'the_user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Favorites Section *************************************************/
+
+		// Favorited topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'favorites_table',
+			'from_fieldname'  => 'the_favorite_topic_id',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => '_bbp_favorites'
+		);
+
+		// Favorited user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'favorites_table',
+			'from_fieldname'  => 'the_user_id',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Setup table joins for the reply section at the base of this section
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_parent_forum_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_parent_topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_author_ip_address',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_author_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply title and reply slugs
+		// Note: We don't actually want either a reply title or a reply slug as
+		//       we want single replies to use their ID as the permalink.
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_content',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply order.
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_order',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'menu_order'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_parent_topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_creation_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_creation_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_modified_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'replies_table',
+			'from_fieldname'  => 'the_reply_modified_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Setup any table joins needed for the reply section
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_table',
+			'from_fieldname'  => 'the_topic_id',
+			'join_tablename'  => 'replies_table',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING topics_table.the_topic_id = replies_table.the_topic_id',
+			'from_expression' => 'WHERE topics_table.first_post != 0',
+			'to_type'         => 'reply'
+		);
+
+		/** User Section ******************************************************/
+
+		// Setup table joins for the user section at the base of this section
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_password_salt',
+			'to_type'         => 'user',
+			'to_fieldname'    => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_class',
+			'default' => 'Example'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_username',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users_table',
+			'from_fieldname' => 'the_users_nicename',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_email_address',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_homepage_url',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_registration_date',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User status.
+		$this->field_map[] = array(
+			'from_tablename' => 'users_table',
+			'from_fieldname' => 'the_users_status',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_status'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users_table',
+			'from_fieldname' => 'the_users_display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User Profile Field 1 (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_custom_profile_field_1',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_example_profile_field_1'
+		);
+
+		// User Profile Field 2 (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_table',
+			'from_fieldname'  => 'the_users_custom_profile_field_2',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_example_profile_field_2'
+		);
+
+		// User Profile Field 3 (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users_table',
+			'from_fieldname' => 'the_users_custom_profile_field_3',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_example_profile_field_3'
+		);
+
+		// Setup any table joins needed for the user section
+		$this->field_map[] = array(
+			'from_tablename'  => 'users_profile_table',
+			'from_fieldname'  => 'the_users_id',
+			'join_tablename'  => 'users_table',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING users_profile_table.the_user_id = users_table.the_user_id',
+			'from_expression' => 'WHERE users_table.the_user_id != -1',
+			'to_type'         => 'user'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/FluxBB.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/FluxBB.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/FluxBB.php	(revision 7162)
@@ -0,0 +1,664 @@
+<?php
+
+/**
+ * bbPress FluxBB Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of FluxBB Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5138)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/fluxbb
+ */
+class FluxBB extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_desc',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'disp_position',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: We join the 'posts' table because 'topics' table does include numeric user id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'posts' table because 'topics' table does not include author IP addresses.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'topics' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open = 0 or Closed = 1, FluxBB v1.5.3)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * FluxBB v1.5.3 Forums do not support topic tags out of the box
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topic_subscriptions',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topic_subscriptions',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+//		$this->field_map[] = array(
+//			'from_tablename' => 'users',
+//			'from_fieldname' => 'salt',
+//			'to_type'        => 'user',
+//			'to_fieldname'   => ''
+//		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'FluxBB'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'registered',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'realname',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_yim'
+		);
+
+	// Store Jabber
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'jabber',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_jabber'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_msn'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_location'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'signature',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Admin Note (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'admin_note',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_fluxbb_user_admin_note'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the post status from FluxBB v1.5.3 numerics to WordPress's strings.
+	 *
+	 * @param int $status FluxBB v1.5.3 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from FluxBB v1.5.3 numerics to WordPress's strings.
+	 *
+	 * @param int $status FluxBB v1.5.3 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // FluxBB Sticky 'sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // FluxBB Normal Topic 'sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count FluxBB v1.5.3 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Invision.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Invision.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Invision.php	(revision 7162)
@@ -0,0 +1,638 @@
+<?php
+
+/**
+ * bbPress Invision Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Invision Power Board v3.x converter.
+ *
+ * @since 2.3.0 bbPress (r4713)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/invision
+ */
+class Invision extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'name_seo',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'position',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'menu_order'
+		);
+
+		// Forum type (Forum = 0 or Category = -1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_date',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_date_gmt',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_modified',
+			'default' => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_modified_gmt',
+			'default' => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'starter_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic content.
+		// Note: We join the posts table because topics do not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON(topics.tid = posts.topic_id) WHERE posts.new_topic = 1',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'pinned',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'start_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'start_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'last_post',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'core_tags',
+			'from_fieldname'  => 'tag_meta_id',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'core_tags',
+			'from_fieldname'  => 'tag_text',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'pid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ip_address',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'author_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edit_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edit_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'member_id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'members_pass_hash',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'members_pass_salt',
+			'to_type'         => 'user',
+			'to_fieldname'    => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_class',
+			'default' => 'Invision'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'email',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'joined',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'members_display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * Translate the forum type from Invision numerics to WordPress's strings.
+	 *
+	 * @param int $status Invision numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == -1 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from Invision numerics to WordPress's strings.
+	 *
+	 * @param int $status Invision numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // Invision Pinned Topic 'pinned = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // Invision Normal Topic 'pinned = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count Invision reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return array( 'hash' => $field, 'salt' => $row['members_pass_salt'] );
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $pass_array['salt'] ) . md5( $this->to_char( $password ) ) ) );
+	}
+
+	private function to_char( $input ) {
+		$output = "";
+		for ( $i = 0; $i < strlen( $input ); $i++ ) {
+			$j = ord( $input[$i] );
+			if ( ( $j >= 65 && $j <= 90 )
+				|| ( $j >= 97 && $j <= 122 )
+				|| ( $j >= 48 && $j <= 57 ) )
+			{
+				$output .= $input[$i];
+			} else {
+				$output .= "&#" . ord( $input[$i] ) . ";";
+			}
+		}
+		return $output;
+	}
+
+	/**
+	 * This callback processes any custom BBCodes with parser.php
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips Invision custom HTML first from $field before parsing $field to parser.php
+		$invision_markup = $field;
+		$invision_markup = html_entity_decode( $invision_markup );
+
+		// Replace '[html]' with '<pre><code>'
+		$invision_markup = preg_replace( '/\[html\]/', '<pre><code>',     $invision_markup );
+		// Replace '[/html]' with '</code></pre>'
+		$invision_markup = preg_replace( '/\[\/html\]/', '</code></pre>', $invision_markup );
+		// Replace '[sql]' with '<pre><code>'
+		$invision_markup = preg_replace( '/\[sql\]/', '<pre><code>',      $invision_markup );
+		// Replace '[/sql]' with '</code></pre>'
+		$invision_markup = preg_replace( '/\[\/sql\]/', '</code></pre>',  $invision_markup );
+		// Replace '[php]' with '<pre><code>'
+		$invision_markup = preg_replace( '/\[php\]/', '<pre><code>',      $invision_markup );
+		// Replace '[/php]' with '</code></pre>'
+		$invision_markup = preg_replace( '/\[\/php\]/', '</code></pre>',  $invision_markup );
+		// Replace '[xml]' with '<pre><code>'
+		$invision_markup = preg_replace( '/\[xml\]/', '<pre><code>',      $invision_markup );
+		// Replace '[/xml]' with '</code></pre>'
+		$invision_markup = preg_replace( '/\[\/xml\]/', '</code></pre>',  $invision_markup );
+		// Replace '[CODE]' with '<pre><code>'
+		$invision_markup = preg_replace( '/\[CODE\]/', '<pre><code>',     $invision_markup );
+		// Replace '[/CODE]' with '</code></pre>'
+		$invision_markup = preg_replace( '/\[\/CODE\]/', '</code></pre>', $invision_markup );
+
+		// Replace '[quote:XXXXXXX]' with '<blockquote>'
+		$invision_markup = preg_replace( '/\[quote:(.*?)\]/', '<blockquote>',                            $invision_markup );
+		// Replace '[quote="$1"]' with '<em>@$1 wrote:</em><blockquote>'
+		$invision_markup = preg_replace( '/\[quote="(.*?)":(.*?)\]/', '<em>@$1 wrote:</em><blockquote>', $invision_markup );
+		// Replace '[/quote:XXXXXXX]' with '</blockquote>'
+		$invision_markup = preg_replace( '/\[\/quote:(.*?)\]/', '</blockquote>',                         $invision_markup );
+
+		// Replace '[twitter]$1[/twitter]' with '<a href="https://twitter.com/$1">@$1</a>"
+		$invision_markup = preg_replace( '/\[twitter\](.*?)\[\/twitter\]/', '<a href="https://twitter.com/$1">@$1</a>', $invision_markup );
+
+		// Replace '[member='username']' with '@username"
+		$invision_markup = preg_replace( '/\[member=\'(.*?)\'\]/', '@$1 ', $invision_markup );
+
+		// Replace '[media]' with ''
+		$invision_markup = preg_replace( '/\[media\]/', '',   $invision_markup );
+		// Replace '[/media]' with ''
+		$invision_markup = preg_replace( '/\[\/media\]/', '', $invision_markup );
+
+		// Replace '[list:XXXXXXX]' with '<ul>'
+		$invision_markup = preg_replace( '/\[list\]/', '<ul>',                    $invision_markup );
+		// Replace '[list=1:XXXXXXX]' with '<ul>'
+		$invision_markup = preg_replace( '/\[list=1\]/', '<ul>',                  $invision_markup );
+		// Replace '[*:XXXXXXX]' with '<li>'
+		$invision_markup = preg_replace( '/\[\*\](.*?)\<br \/\>/', '<li>$1</li>', $invision_markup );
+		// Replace '[/list:u:XXXXXXX]' with '</ul>'
+		$invision_markup = preg_replace( '/\[\/list\]/', '</ul>',                 $invision_markup );
+
+		// Replace '[hr]' with '<hr>"
+		$invision_markup = preg_replace( '/\[hr\]/', '<hr>',     $invision_markup );
+
+		// Replace '[font=XXXXXXX]' with ''
+		$invision_markup = preg_replace( '/\[font=(.*?)\]/', '', $invision_markup );
+		// Replace '[/font]' with ''
+		$invision_markup = preg_replace( '/\[\/font\]/', '',     $invision_markup );
+
+		// Replace any Invision smilies from path '/sp-resources/forum-smileys/sf-smily.gif' with the equivelant WordPress Smilie
+		$invision_markup = preg_replace( '/\<img src=(.*?)EMO\_DIR(.*?)bbc_emoticon(.*?)alt=\'(.*?)\' \/\>/', '$4', $invision_markup );
+		$invision_markup = preg_replace( '/\:angry\:/',    ':mad:',     $invision_markup );
+		$invision_markup = preg_replace( '/\:mellow\:/',   ':neutral:', $invision_markup );
+		$invision_markup = preg_replace( '/\:blink\:/',    ':eek:',     $invision_markup );
+		$invision_markup = preg_replace( '/B\)/',          ':cool:',    $invision_markup );
+		$invision_markup = preg_replace( '/\:rolleyes\:/', ':roll:',    $invision_markup );
+		$invision_markup = preg_replace( '/\:unsure\:/',   ':???:',     $invision_markup );
+
+		// Now that Invision custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $invision_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Kunena1.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Kunena1.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Kunena1.php	(revision 7162)
@@ -0,0 +1,523 @@
+<?php
+
+/**
+ * bbPress Kunena 1.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Kunena v1.x Forums for Joomla Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5144)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/kunena/
+ */
+class Kunena1 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'parent',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numTopics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numPosts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numTopics',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numPosts',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'ordering',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum > 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'parent',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Open = 0 or Closed = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'thread',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'catid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'ip',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'kunena_messages_text' table because 'kunena_messages' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages_text',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'kunena_messages',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON kunena_messages_text.mesid = kunena_messages.id WHERE kunena_messages.parent = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'catid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open or Closed, Kunena v3.x 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * Kunena v1.x Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'id',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'catid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		// Note: We join the 'kunena_messages_text' table because 'kunena_messages' table does not include reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages_text',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'kunena_messages',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON kunena_messages.id = kunena_messages_text.mesid WHERE kunena_messages.parent != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		//Note: We are importing the Joomla User details and the Kunena v1.x user profile details.
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+//		$this->field_map[] = array(
+//			'from_tablename' => 'user',
+//			'from_fieldname' => 'salt',
+//			'to_type'        => 'user',
+//			'to_fieldname'   => ''
+//		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+//		$this->field_map[] = array(
+//			'to_type'      => 'user',
+//			'to_fieldname' => '_bbp_class',
+//			'default'      => 'Kunena1'
+//		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'registerDate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass($field, $row) {
+		$pass_array = array('hash' => $field, 'salt' => $row['salt']);
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass($password, $serialized_pass) {
+		$pass_array = unserialize($serialized_pass);
+		return ( $pass_array['hash'] == md5(md5($password) . $pass_array['salt']) );
+	}
+
+	/**
+	 * Translate the forum type from Kunena v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == 0 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from Kunena v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v1.x numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from Kunena v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v1.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Kunena2.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Kunena2.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Kunena2.php	(revision 7162)
@@ -0,0 +1,564 @@
+<?php
+
+/**
+ * bbPress Kunena 2.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Kunena v2.x Forums for Joomla Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5144)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/kunena/
+ */
+class Kunena2 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numTopics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numPosts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numTopics',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numPosts',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'alias',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'ordering',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum = >0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Open = 0 or Closed = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_topics',
+			'from_fieldname' => 'id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'category_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_userid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'kunena_messages' table because 'kunena_topics' table does not include author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'ip',
+			'join_tablename'  => 'kunena_topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (id)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_message',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_topics',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'category_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open or Closed, Kunena v2.x 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * Kunena v2.x Forums do not support topic tags out of the box
+		 */
+
+ 		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'catid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		// Note: We join the 'kunena_messages_text' table because 'kunena_messages' table does not include reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages_text',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'kunena_messages',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_messages_text.mesid = kunena_messages.id LEFT JOIN jos_kunena_topics AS kunena_topics ON kunena_messages.thread = kunena_topics.id WHERE kunena_messages.parent != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		//Note: We are importing the Joomla User details and the Kunena v2.x user profile details.
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt. This is only used for the SELECT row info for the above password save
+		/*
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'salt',
+			'to_type' => 'user',
+			'to_fieldname' => ''
+		);
+		 */
+
+		// User password verify class. Stores in usermeta for verifying password.
+		/*
+		$this->field_map[] = array(
+			'to_type' => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default' => 'Kunena2'
+		);
+		 */
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'registerDate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass($field, $row) {
+		$pass_array = array('hash' => $field, 'salt' => $row['salt']);
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass($password, $serialized_pass) {
+		$pass_array = unserialize($serialized_pass);
+		return ( $pass_array['hash'] == md5(md5($password) . $pass_array['salt']) );
+	}
+	/**
+	 * Translate the forum type from Kunena v2.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v2.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == 0 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from Kunena v2.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v2.x numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from Kunena v2.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v2.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count Kunena v2.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Kunena3.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Kunena3.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Kunena3.php	(revision 7162)
@@ -0,0 +1,789 @@
+<?php
+
+/**
+ * bbPress Kunena 3.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Kunena v3.x Forums for Joomla Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5144)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/kunena/
+ */
+class Kunena3 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numTopics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'numPosts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numTopics',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'numPosts',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'alias',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_categories',
+			'from_fieldname' => 'ordering',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum = >0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Open = 0 or Closed = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_categories',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_topics',
+			'from_fieldname' => 'id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'category_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_userid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'kunena_messages' table because 'kunena_topics' table does not include author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'ip',
+			'join_tablename'  => 'kunena_topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (id)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_message',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_topics',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'category_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'first_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open or Closed, Kunena v3.x 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_topics',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * Kunena v3.x Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section ******************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'catid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'kunena_messages',
+			'from_fieldname' => 'ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		// Note: We join the 'kunena_messages_text' table because 'kunena_messages' table does not include reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages_text',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'kunena_messages',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_messages_text.mesid = kunena_messages.id LEFT JOIN jos_kunena_topics AS kunena_topics ON kunena_messages.thread = kunena_topics.id WHERE kunena_messages.parent != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_messages',
+			'from_fieldname'  => 'time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		//Note: We are importing the Joomla User details and the Kunena v3.x user profile details.
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+//		$this->field_map[] = array(
+//			'from_tablename' => 'users',
+//			'from_fieldname' => 'salt',
+//			'to_type'        => 'user',
+//			'to_fieldname'   => ''
+//		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'Kunena3'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'websiteurl',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'registerDate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'aim',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'yim',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_yim'
+		);
+
+		// Store Google Tak (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'gtalk',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_jabber'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'icq',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'msn',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_msn'
+		);
+
+		// Store Skype (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'skype',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_skype'
+		);
+
+		// Store Twitter (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'twitter',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_twitter'
+		);
+
+		// Store Facebook (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'facebook',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_facebook'
+		);
+
+		// Store myspace (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'myspace',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_myspace'
+		);
+
+		// Store linkedin (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'linkedin',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_linkedin'
+		);
+
+		// Store delicious (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'delicious',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_delicious'
+		);
+
+		// Store friendfeed (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'friendfeed',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_friendfeed'
+		);
+
+		// Store digg (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'digg',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_digg'
+		);
+
+		// Store blogspot (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'blogspot',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_blogspot'
+		);
+
+		// Store flickr (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'flickr',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_flickr'
+		);
+
+		// Store bebo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'bebo',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_bebo'
+		);
+
+		// Store websitename (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'websitename',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_websitename'
+		);
+
+		// Store location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'location',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_location'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'kunena_users',
+			'from_fieldname'  => 'signature',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON kunena_users.userid = users.id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_kunena3_user_sig',
+			'callback_method' => 'callback_html'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from Kunena v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v3.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == 0 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from Kunena v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v3.x numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from Kunena v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Kunena v3.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count Kunena v3.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Mingle.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Mingle.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Mingle.php	(revision 7162)
@@ -0,0 +1,480 @@
+<?php
+
+/**
+ * bbPress Mingle Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Mingle Forums converter.
+ *
+ * @since 2.3.0 bbPress (r4691)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/mingle
+ */
+class Mingle extends BBP_Converter_Base {
+
+	/**
+	 * Main constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals()	{
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section ******************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_forums',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_forums',
+			'from_fieldname' => 'parent_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_forums',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_forums',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_forums',
+			'from_fieldname' => 'sort',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_date',
+			'default'        => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_date_gmt',
+			'default'        => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_modified',
+			'default'        => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_modified_gmt',
+			'default'        => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section ******************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_threads',
+			'from_fieldname' => 'id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'starter',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic content.
+		// Note: We join the forum_posts table because forum_topics do not have topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'text',
+			'join_tablename'  => 'forum_threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON forum_posts.parent_id = forum_threads.id GROUP BY forum_threads.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_threads',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_threads',
+			'from_fieldname' => 'last_post',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * Mingle Forums do not support topic tags
+		 */
+
+		/** Reply Section ******************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_posts',
+			'from_fieldname' => 'id',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Setup reply section table joins
+		// We need join the 'forum_threads' table to only import replies
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_threads',
+			'from_fieldname'  => 'date',
+			'join_tablename'  => 'forum_posts',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON forum_posts.parent_id = forum_threads.id',
+			'from_expression' => 'WHERE forum_threads.subject != forum_posts.subject',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_last_active_time'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'author_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'text',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_posts',
+			'from_fieldname'  => 'date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'ID',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_pass',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_password'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_login',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_nicename',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_registered',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_registered'
+		);
+
+		// User status.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_status',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_status'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together. That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return false;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		return false;
+	}
+
+	/**
+	 * Translate the topic status from Mingle numerics to WordPress's strings.
+	 *
+	 * @param int $status Mingle v1.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from Mingle numerics to WordPress's strings.
+	 *
+	 * @param int $status Mingle numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 'sticky' :
+				$status = 'sticky';       // Mingle Sticky 'status = sticky'
+				break;
+
+			case 'open'  :
+			default :
+				$status = 'normal';       // Mingle Normal Topic 'status = open'
+				break;
+		}
+		return $status;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/MyBB.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/MyBB.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/MyBB.php	(revision 7162)
@@ -0,0 +1,590 @@
+<?php
+
+/**
+ * bbPress MyBB Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of MyBB Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5140)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/mybb
+ */
+class MyBB extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'fid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'pid',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'threads',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'disporder',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'threads',
+			'from_fieldname' => 'tid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'uid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'ipaddress',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE replyto = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'threads' table does not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE replyto = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'threads',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'lastpost',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'lastpost',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'lastpost',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open or Closed, MyBB v1.6.10 open = null & closed = 1)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * MyBB v1.6.10 Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'pid',
+			'from_expression' => 'WHERE replyto != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'ipaddress',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'uid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edittime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edittime',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'uid',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'MyBB'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'website',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'regdate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'usertitle',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_mybb_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_mybb_user_yim'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_mybb_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_mybb_user_msn'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'signature',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_mybb_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the post status from MyBB v1.6.10 numerics to WordPress's strings.
+	 *
+	 * @param int $status MyBB v1.6.10 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from MyBB v1.6.10 numerics to WordPress's strings.
+	 *
+	 * @param int $status MyBB v1.6.10 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // MyBB Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // MyBB Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count MyBB v1.6.10 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/PHPFox3.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/PHPFox3.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/PHPFox3.php	(revision 7162)
@@ -0,0 +1,620 @@
+<?php
+
+/**
+ * bbPress PHPFox3 Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of PHPFox v3.x Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5146)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/phpfox
+ */
+class PHPFox3 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'total_thread',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'total_post',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'total_thread',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'total_post',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'name_url',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'ordering',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Forum = 0 or Category = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'is_category',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (0=Open or 1=Closed, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'is_closed',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_thread',
+			'from_fieldname' => 'thread_id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'total_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'total_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic content.
+		// Note: We join the 'forum_post_text' table because 'forum_thread' table does not include content.
+		// Note: We can use 'text' for original text OR 'text_parsed' for pre-parsed text output
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post_text',
+			'from_fieldname'  => 'text_parsed',
+			'join_tablename'  => 'forum_thread',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON forum_thread.start_id = forum_post_text.post_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_thread',
+			'from_fieldname' => 'title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'title_url',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Open or Closed, PHPFox v3.5.x 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'is_closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'order_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'time_update',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'time_update',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'time_update',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'item_id',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tag_id',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tag_text',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tag_url',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'post_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_thread',
+			'from_fieldname'  => 'forum_id',
+			'join_tablename'  => 'forum_post',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (thread_id)',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		// Note: We join the 'forum_post_text' table because 'forum_post' table does not include content.
+		// Note: We can use 'text' for original text OR 'text_parsed' for pre-parsed text output
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post_text',
+			'from_fieldname'  => 'text_parsed',
+			'join_tablename'  => 'forum_post',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON forum_post_text.post_id = forum_post.post_id WHERE forum_thread.start_id != forum_post.post_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_post',
+			'from_fieldname'  => 'time_stamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'password_salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'PHPFox3'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'joined',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'full_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from PHPFox v3.5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status PHPFox v3.5.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'category';
+				break;
+
+			case 0  :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from PHPFox v3.5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status PHPFox v3.5.x numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from PHPFox v3.5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status PHPFox v3.5.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from PHPFox v3.5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status PHPFox v3.5.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // PHPFox Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // PHPFox Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count PHPFox v3.5.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/PHPWind.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/PHPWind.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/PHPWind.php	(revision 7162)
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * bbPress PHPWind Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of PHPWind Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5142)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/phpwind
+ */
+class PHPWind extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_forum',
+			'from_fieldname' => 'fid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum',
+			'from_fieldname'  => 'parentid',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		// Note: We join the 'bbs_forum_statistics' table because 'bbs_forum' table does not include topic and reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum_statistics',
+			'from_fieldname'  => 'threads',
+			'join_tablename'  => 'bbs_forum',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (fid)',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		// Note: We join the 'bbs_forum_statistics' table because 'bbs_forum' table does not include topic and reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum_statistics',
+			'from_fieldname'  => 'posts',
+			'join_tablename'  => 'bbs_forum',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (fid)',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		// Note: We join the 'bbs_forum_statistics' table because 'bbs_forum' table does not include topic and reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum_statistics',
+			'from_fieldname'  => 'threads',
+			'join_tablename'  => 'bbs_forum',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (fid)',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		// Note: We join the 'bbs_forum_statistics' table because 'bbs_forum' table does not include topic and reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum_statistics',
+			'from_fieldname'  => 'posts',
+			'join_tablename'  => 'bbs_forum',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (fid)',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_forum',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum',
+			'from_fieldname'  => 'descrip',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_forum',
+			'from_fieldname' => 'vieworder',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = category or Forum = forum, sub or sub2,  Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_forum',
+			'from_fieldname'  => 'type',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'bbs_forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'bbs_forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'bbs_forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'bbs_forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_threads',
+			'from_fieldname' => 'tid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'created_userid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'created_ip',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'bbs_threads_content' table because 'bbs_threads' table does not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads_content',
+			'from_fieldname'  => 'content',
+			'join_tablename'  => 'bbs_threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_threads',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'lastpost_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'lastpost_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'lastpost_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open or Closed, PHPWind v9.x 0=no, 1=closed & 2=open)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_threads',
+			'from_fieldname'  => 'tpcstatus',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * PHPWind v9.x Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_posts',
+			'from_fieldname' => 'pid',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'bbs_posts',
+			'from_fieldname' => 'created_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'created_userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'content',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'bbs_posts',
+			'from_fieldname'  => 'created_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'uid',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password'
+//			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+/*		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'pass',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+*/
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'PHPWind'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'regdate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'realname',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from PHPWind v9.x Capitalised case to WordPress's non-capatilise case strings.
+	 *
+	 * @param int $status PHPWind v9.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 1 ) {
+		switch ( $status ) {
+			case 'category' :
+				$status = 'category';
+				break;
+
+			case 'sub' :
+				$status = 'forum';
+				break;
+
+			case 'sub2' :
+				$status = 'forum';
+				break;
+
+			case 'forum' :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from PHPWind v9.x numerics to WordPress's strings.
+	 *
+	 * @param int $status PHPWind v9.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 2 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 2  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count PHPWind v9.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+}
Index: /tags/2.6.6/src/includes/admin/converters/Phorum.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Phorum.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Phorum.php	(revision 7162)
@@ -0,0 +1,590 @@
+<?php
+
+/**
+ * bbPress Phorum Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Phorum Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5141)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/phorum
+ */
+class Phorum extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'parent_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'thread_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'message_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'display_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 1 or Forum = 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'folder_flag',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'thread',
+			'from_expression' => 'WHERE parent_id = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'thread_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'thread_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename' => 'messages',
+			'from_fieldname' => 'author',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'ip',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'body',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'messages',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// Topic status (Open = 0 or Closed = 1)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * Phorum v5.2.19 Forums do not support topic tags out of the box
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribers',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribers',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'message_id',
+			'from_expression' => 'WHERE parent_id != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'messages',
+			'from_fieldname' => 'ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'author',
+			'to_type'         => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous  (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'body',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+//		$this->field_map[] = array(
+//			'from_tablename' => 'users',
+//			'from_fieldname' => 'salt',
+//			'to_type'        => 'user',
+//			'to_fieldname'   => ''
+//		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'Phorum'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'date_added',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'real_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'signature',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phorum_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from Phorum v5.2.19 numerics to WordPress's strings.
+	 *
+	 * @param int $status Phorum v5.2.19 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'category';
+				break;
+
+			case 0  :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from Phorum v5.2.19 numerics to WordPress's strings.
+	 *
+	 * @param int $status Phorum v5.2.19 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count Phorum v5.2.19 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/PunBB.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/PunBB.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/PunBB.php	(revision 7162)
@@ -0,0 +1,737 @@
+<?php
+
+/**
+ * bbPress PunBB Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of PunBB v1.4.2 Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5153)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/punbb
+ */
+class PunBB extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_desc',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'disp_position',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_subscriptions',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: We join the 'posts' table because 'topics' table does not have user id's.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'poster',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'posts' table because 'topics' table does not have author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'topics' table does not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.first_post_id = posts.id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Open or Closed, PunBB v1.4.2 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'last_post',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * PunBB v1.4.2 Forums do not support topic tags out of the box
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscriptions',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscriptions',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		// Note: We join the 'topics' table because 'posts' table does not have parent forum id's.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'join_tablename'  => 'posts',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id = posts.topic_id WHERE topics.first_post_id != posts.id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'posted',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edited',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'edited',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		// Don't import user id 1, this is PunBB's guest user
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'id',
+			'from_expression' => 'WHERE id != 1',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'users',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'PunBB'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'registered',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'realname',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_yim'
+		);
+
+		// Store Jabber
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'jabber',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_jabber'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_msn'
+		);
+
+		// Store Facebook (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'facebook',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_facebook'
+		);
+
+		// Store Twitter (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'twitter',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_twitter'
+		);
+
+		// Store LinkedIn (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'linkedin',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_linkedin'
+		);
+
+		// Store Skype (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'skype',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_skype'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'signature',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_from'
+		);
+
+		// Store Admin Note (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'admin_note',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_punbb_user_admin_note'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the post status from PunBB v1.4.2 numerics to WordPress's strings.
+	 *
+	 * @param int $status PunBB v1.4.2 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from PunBB v1.4.2 numerics to WordPress's strings.
+	 *
+	 * @param int $status PunBB v1.4.2 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // PunBB Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // PunBB Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count PunBB v1.4.2 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/SMF.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/SMF.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/SMF.php	(revision 7162)
@@ -0,0 +1,812 @@
+<?php
+
+/**
+ * bbPress SMF Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of SMF Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5189)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/smf
+ */
+class SMF extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section ******************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'id_board',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'boards',
+			'from_fieldname'  => 'id_parent',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'num_topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'num_posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'boards',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'boards',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'boards',
+			'from_fieldname' => 'board_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'log_notify',
+			'from_fieldname'  => 'id_board',
+			'from_expression' => 'WHERE log_notify.id_board != 0',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'log_notify',
+			'from_fieldname'  => 'id_member',
+			'from_expression' => 'WHERE log_notify.id_board != 0',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section ******************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'id_topic',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'num_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'id_board',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'id_member_started',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_name',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'id_member_started',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'messages' table because 'topics' table does not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'body',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'subject',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'subject',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'id_board',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Open or Closed, SMF v2.0.4 0=open & 1=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'locked',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'is_sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON topics.id_first_msg = messages.id_msg',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * SMF v2.0.4 Forums do not support topic tags out of the box
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'log_notify',
+			'from_fieldname'  => 'id_topic',
+			'from_expression' => 'WHERE log_notify.id_topic != 0',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'log_notify',
+			'from_fieldname'  => 'id_member',
+			'from_expression' => 'WHERE log_notify.id_topic != 0',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'id_msg',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'id_board',
+			'join_tablename'  => 'messages',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (id_topic) WHERE topics.id_first_msg != messages.id_msg',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'id_topic',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'messages',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'id_member',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_name',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'id_member',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'body',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'id_topic',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'messages',
+			'from_fieldname'  => 'poster_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'id_member',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'passwd',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'SMF'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'member_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'member_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'email_address',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'website_url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'date_registered',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'real_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'yim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_yim'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_msn'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'signature',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Avatar (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'avatar',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_avatar',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_location',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Personal Text (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'personal_text',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_smf_user_personal_text',
+			'callback_method' => 'callback_html'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'username' => $row['member_name'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] === sha1( strtolower( $pass_array['username'] ) . $password ) ? true : false );
+	}
+
+	/**
+	 * Translate the post status from SMF v2.0.4 numerics to WordPress's strings.
+	 *
+	 * @param int $status SMF v2.0.4 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from SMF v2.0.4 numerics to WordPress's strings.
+	 *
+	 * @param int $status SMF v2.0.4 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky'; // SMF Sticky 'is_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal'; // SMF normal topic 'is_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count SMF v2.0.4 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips SMF custom HTML first from $field before parsing $field to parser.php
+		$SMF_markup = $field;
+		$SMF_markup = html_entity_decode( $SMF_markup );
+
+		// Replace '[quote]' with '<blockquote>'
+		$SMF_markup = preg_replace( '/\[quote\]/',        '<blockquote>',  $SMF_markup );
+		// Replace '[quote ($1)]' with '<blockquote>"
+		$SMF_markup = preg_replace( '/\[quote (.*?)\]/' , '<blockquote>',  $SMF_markup );
+		// Replace '[/quote]' with '</blockquote>'
+		$SMF_markup = preg_replace( '/\[\/quote\]/',      '</blockquote>', $SMF_markup );
+
+		// Replace '[glow]' with ''
+		$SMF_markup = preg_replace( '/\[glow\]/',   '',       $SMF_markup );
+		// Replace '[glow]' with ''
+		$SMF_markup = preg_replace( '/\[glow=(.*?)\]/',   '', $SMF_markup );
+		// Replace '[/glow]' with ''
+		$SMF_markup = preg_replace( '/\[\/glow\]/', '',       $SMF_markup );
+
+		// Replace '[shadow]' with ''
+		$SMF_markup = preg_replace( '/\[shadow\]/',   '',       $SMF_markup );
+		// Replace '[shadow]' with ''
+		$SMF_markup = preg_replace( '/\[shadow=(.*?)\]/',   '', $SMF_markup );
+		// Replace '[/shadow]' with ''
+		$SMF_markup = preg_replace( '/\[\/shadow\]/', '',       $SMF_markup );
+
+		// Replace '[move]' with ''
+		$SMF_markup = preg_replace( '/\[move\]/',   '', $SMF_markup );
+		// Replace '[/move]' with ''
+		$SMF_markup = preg_replace( '/\[\/move\]/', '', $SMF_markup );
+
+		// Replace '[table]' with '<table>'
+		$SMF_markup = preg_replace( '/\[table\]/',   '<table>',  $SMF_markup );
+		// Replace '[/table]' with '</table>'
+		$SMF_markup = preg_replace( '/\[\/table\]/', '</table>', $SMF_markup );
+		// Replace '[tr]' with '<tr>'
+		$SMF_markup = preg_replace( '/\[tr\]/',   '<tr>',  $SMF_markup );
+		// Replace '[/tr]' with '</tr>'
+		$SMF_markup = preg_replace( '/\[\/tr\]/', '</tr>', $SMF_markup );
+		// Replace '[td]' with '<td>'
+		$SMF_markup = preg_replace( '/\[td\]/',   '<td>',  $SMF_markup );
+		// Replace '[/td]' with '</td>'
+		$SMF_markup = preg_replace( '/\[\/td\]/', '</td>', $SMF_markup );
+
+		// Replace '[list]' with '<ul>'
+		$SMF_markup = preg_replace( '/\[list\]/',     '<ul>',                      $SMF_markup );
+		// Replace '[liist type=decimal]' with '<ol type="a">'
+		$SMF_markup = preg_replace( '/\[list\ type=decimal\]/',   '<ol type="a">', $SMF_markup );
+		// Replace '[li]' with '<li>'
+		$SMF_markup = preg_replace( '/\[li\]/',   '<li>',  $SMF_markup );
+		// Replace '[/li]' with '</li>'
+		$SMF_markup = preg_replace( '/\[\/li\]/', '</li>', $SMF_markup );
+
+		// Replace '[tt]' with '<tt>'
+		$SMF_markup = preg_replace( '/\[tt\]/',   '<tt>',  $SMF_markup );
+		// Replace '[/tt]' with '</tt>'
+		$SMF_markup = preg_replace( '/\[\/tt\]/', '</tt>', $SMF_markup );
+
+		// Replace '<br />' with '<br>'
+		$SMF_markup = preg_replace( '/\<br \/\>/', '<br>', $SMF_markup );
+
+		// Replace '[size=$1]' with '<span style="font-size:$1%;">$3</span>'
+		$SMF_markup = preg_replace( '/\[size=(.*?)\]/', '<span style="font-size:$1">', $SMF_markup );
+		// Replace '[/size]' with '</span>'
+		$SMF_markup = preg_replace( '/\[\/size\]/',     '</span>',                     $SMF_markup );
+
+		// Replace non-break space '&nbsp;' with space ' '
+		$SMF_markup = preg_replace( '/&nbsp;/', ' ', $SMF_markup );
+
+		// Now that SMF custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $SMF_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/SimplePress5.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/SimplePress5.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/SimplePress5.php	(revision 7162)
@@ -0,0 +1,616 @@
+<?php
+
+/**
+ * bbPress SimplePress5 Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of SimplePress v5 converter.
+ *
+ * @since 2.3.0 bbPress (r4638)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/simplepress/
+ */
+class SimplePress5 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'parent',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'topic_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'post_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'topic_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'post_count',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfforums',
+			'from_fieldname'  => 'forum_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfforums',
+			'from_fieldname'  => 'forum_desc',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfforums',
+			'from_fieldname' => 'forum_seq',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forums',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forums',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forums',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forums',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sftopics',
+			'from_fieldname' => 'topic_id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sftopics',
+			'from_fieldname' => 'post_count',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'sftopics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE sfposts.post_index = 1',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'guest_name',
+			'join_tablename'  => 'sftopics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE sfposts.post_index = 1',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic content.
+		// Note: We join the sfposts table because sftopics do not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_content',
+			'join_tablename'  => 'sftopics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE sfposts.post_index = 1',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'sftopics',
+			'from_fieldname' => 'topic_name',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_name',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_pinned',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sftopics',
+			'from_fieldname'  => 'topic_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * SimplePress Forums do not support topic tags without paid extensions
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfposts',
+			'from_fieldname' => 'post_id',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'forum_id',
+			'from_expression' => 'WHERE post_index != 1',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'sfposts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'guest_name',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_content',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'sfposts',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'ID',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_pass',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_password'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_login',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_nicename',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_registered',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_registered'
+		);
+
+		// User status.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_status',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_status'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together. That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return false;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		return false;
+	}
+
+	/**
+	 * Translate the post status from Simple:Press v5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Simple:Press numeric status
+	 * @return string WordPress safe
+	 */
+	public function callback_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from Simple:Press v5.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Simple:Press v5.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // Simple:Press Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // Simple:Press Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count Simple:Press v5.x reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom HTML code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strip any custom HTML not supported by parser.php first from $field before parsing $field to parser.php
+		$simplepress_markup = $field;
+		$simplepress_markup = html_entity_decode( $simplepress_markup );
+
+		// Replace any SimplePress smilies from path '/sp-resources/forum-smileys/sf-smily.gif' with the equivelant WordPress Smilie
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-confused\.gif(.*?)\" \/>/',     ':?' ,     $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-cool\.gif(.*?)\" \/>/',        ':cool:',   $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-cry\.gif(.*?)\" \/>/',         ':cry:',    $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-embarassed\.gif(.*?)\" \/>/' , ':oops:',   $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-frown\.gif(.*?)\" \/>/',       ':(',       $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-kiss\.gif(.*?)\" \/>/',        ':P',       $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-laugh\.gif(.*?)\" \/>/',       ':D',       $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-smile\.gif(.*?)\" \/>/',       ':smile:',  $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-surprised\.gif(.*?)\" \/>/',   ':o',       $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-wink\.gif(.*?)\" \/>/',        ':wink:',   $simplepress_markup );
+		$simplepress_markup = preg_replace( '/\<img src=(.*?)\/sp-resources\/forum-smileys\/sf-yell\.gif(.*?)\" \/>/',         ':x',      $simplepress_markup );
+
+		// Replace '<div class="sfcode">example code</div>' with '<code>*</code>'
+		$simplepress_markup = preg_replace( '/\<div class\=\"sfcode\"\>(.*?)\<\/div\>/' , '<code>$1</code>' , $simplepress_markup );
+
+		// Replace '<strong>username said </strong>' with '@username said:'
+		$simplepress_markup = preg_replace( '/\<strong\>(.*?)\ said\ \<\/strong\>/',     '@$1 said:',        $simplepress_markup );
+
+		// Replace '<p>&nbsp;</p>' with '<p>&nbsp;</p>'
+		$simplepress_markup = preg_replace( '/\n(&nbsp;|[\s\p{Z}\xA0\x{00A0}]+)\r/', '<br>', $simplepress_markup );
+
+		// Now that SimplePress' custom HTML codes have been stripped put the cleaned HTML back in $field
+		$field = $simplepress_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/Vanilla.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/Vanilla.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/Vanilla.php	(revision 7162)
@@ -0,0 +1,608 @@
+<?php
+
+/**
+ * bbPress Vanilla Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of Vanilla 2.0.18.1 Converter
+ *
+ * @since 2.3.0 bbPress (r4717)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/vanilla
+ */
+class Vanilla extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Category',
+			'from_fieldname'  => 'CategoryID',
+			'from_expression' => 'WHERE Category.CategoryID > 0',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Category',
+			'from_fieldname'  => 'ParentCategoryID',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id',
+			'callback_method' => 'callback_forum_parent'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'CountDiscussions',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'CountComments',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'CountDiscussions',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'CountComments',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'Name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Category',
+			'from_fieldname'  => 'Name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Category',
+			'from_fieldname'  => 'Description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'Sort',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Set a default value 'forum', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_forum_type',
+			'default'      => 'forum'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_date',
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_date_gmt',
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_modified',
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Category',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_modified_gmt',
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		// Don't import Vanilla 2's deleted topics
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'DiscussionID',
+			'from_expression' => 'WHERE Format != "Deleted"',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'CountComments',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'CountComments',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'CategoryID',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'InsertUserID',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'to_type'      => 'topic',
+			'to_fieldname' => '_bbp_old_topic_author_name_id',
+			'default'      => 'Anonymous'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'InsertUserID',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'Name',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'Name',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'Body',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'InsertIPAddress',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'CategoryID',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Discussion',
+			'from_fieldname'  => 'Announce',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_modified_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Discussion',
+			'from_fieldname' => 'DateLastComment',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'TagDiscussion',
+			'from_fieldname'  => 'DiscussionID',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'TagDiscussion',
+			'from_fieldname'  => 'TagID',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Tag',
+			'from_fieldname'  => 'Name',
+			'join_tablename'  => 'TagDiscussion',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tagid)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		// Don't import Vanilla 2's deleted replies
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'CommentID',
+			'from_expression' => 'WHERE Format != "Deleted"',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'DiscussionID',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'DiscussionID',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'Comment',
+			'from_fieldname' => 'InsertIPAddress',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'InsertUserID',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'to_type'      => 'reply',
+			'to_fieldname' => '_bbp_old_reply_author_name_id',
+			'default'      => 'Anonymous'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'InsertUserID',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'Body',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'Comment',
+			'from_fieldname'  => 'DiscussionID',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename' => 'Comment',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Comment',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Comment',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'Comment',
+			'from_fieldname' => 'DateUpdated',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_modified_gmt'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		// Don't import user Vanilla's deleted users
+		$this->field_map[] = array(
+			'from_tablename'  => 'User',
+			'from_fieldname'  => 'UserID',
+			'from_expression' => 'WHERE Deleted !=1',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'Password',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_password'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'Name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'Name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'Email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'DateInserted',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_registered'
+		);
+
+		// Display Name
+		$this->field_map[] = array(
+			'from_tablename' => 'User',
+			'from_fieldname' => 'Name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * Translate the topic status from Vanilla v2.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Vanilla v2.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from Vanilla v2.x numerics to WordPress's strings.
+	 *
+	 * @param int $status Vanilla v2.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // Vanilla Sticky 'Announce = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // Vanilla normal topic 'Announce = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Clean Root Parent ID -1 to 0
+	 *
+	 * @param int $parent Vanilla v2.x Parent ID
+	 * @return int
+	 */
+	public function callback_forum_parent( $parent = 0 ) {
+		if ( $parent == -1 ) {
+			return 0;
+		} else {
+			return $parent;
+		}
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count Vanilla v2.x reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This method is to save the salt and password together. That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return false;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		return false;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/XMB.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/XMB.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/XMB.php	(revision 7162)
@@ -0,0 +1,721 @@
+<?php
+
+/**
+ * bbPress XMB Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of XMB Forum converter.
+ *
+ * @since 2.5.0 bbPress (r5143)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/xmb
+ */
+class XMB extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'fid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'fup',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'threads',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'threads',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'displayorder',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 'group', Forum = 'forum' or 'sub', Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'type',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'threads',
+			'from_fieldname' => 'tid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: We join the 'members' table because 'threads' table does not have numerical author id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON threads.author = members.username',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'author',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON threads.author = members.username  WHERE posts.subject = ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'posts' table because 'threads' table does not have author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'useip',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'threads' table does not have content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'threads',
+			'from_fieldname' => 'subject',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'subject',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic status (Open or Closed, XMB v1.9.11.13 ''=open & 'yes'=closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'closed',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'threads',
+			'from_fieldname'  => 'topped',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		// Note: We join the 'posts' table because 'threads' table does not include dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'join_tablename'  => 'threads',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tid) WHERE posts.subject != ""',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * XMB v1.9.11.13 Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'pid',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'fid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'useip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		// Note: We join the 'members' table because 'posts' table does not have numerical author id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'posts',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON posts.author = members.username WHERE posts.subject = ""',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'author',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'uid',
+			'join_tablename'  => 'posts',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON posts.author = members.username WHERE posts.subject = ""',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'message',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'tid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'uid',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old User password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old User Salt (This is only used for the SELECT row info for the above password save)
+//		$this->field_map[] = array(
+//			'from_tablename' => 'members',
+//			'from_fieldname' => 'salt',
+//			'to_type'        => 'user',
+//			'to_fieldname'   => ''
+//		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'members',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'XMB'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'site',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'regdate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_yim'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_icq'
+		);
+
+		// Store MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_msn'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'sig',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xmb_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Bio (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'members',
+			'from_fieldname'  => 'bio',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xmb_user_bio',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_location'
+		);
+
+		// Store Avatar (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'avatar',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_avatar'
+		);
+
+		// Store Mood (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'members',
+			'from_fieldname' => 'mood',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_xmb_user_mood'
+		);
+
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from XMB v1.9.11.13 Capitalised case to WordPress's non-capatilise case strings.
+	 *
+	 * @param int $status XMB v1.9.11.13 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 1 ) {
+		switch ( $status ) {
+			case 'group' :
+				$status = 'category';
+				break;
+
+			case 'sub' :
+				$status = 'forum';
+				break;
+
+			case 'forum' :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from XMB v1.9.11.13 numerics to WordPress's strings.
+	 *
+	 * @param int $status XMB v1.9.11.13 numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = '' ) {
+		switch ( $status ) {
+			case 'yes' :
+				$status = 'closed';
+				break;
+
+			case ''  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from XMB v1.9.11.13 numerics to WordPress's strings.
+	 *
+	 * @param int $status XMB v1.9.11.13 numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // XMB Sticky 'topped = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // XMB Normal Topic 'topped = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count XMB v1.9.11.13 topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/XenForo.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/XenForo.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/XenForo.php	(revision 7162)
@@ -0,0 +1,850 @@
+<?php
+
+/**
+ * bbPress XenForo Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ *  Implementation of XenForo converter.
+ *
+ * @since 2.5.0 bbPress (r5145)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/xenforo
+ */
+class XenForo extends BBP_Converter_Base {
+
+	/**
+	 * Main constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'node_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'parent_node_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		// Note: We join the 'forum' table because 'node' does not include topic counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'discussion_count',
+			'join_tablename'  => 'node',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (node_id) WHERE node.node_type_id = "Category" OR node.node_type_id = "Forum" ',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		// Note: We join the 'forum' table because 'node' does not include reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'message_count',
+			'join_tablename'  => 'node',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (node_id) WHERE node.node_type_id = "Category" OR node.node_type_id = "Forum" ',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		// Note: We join the 'forum' table because 'node' does not include topic counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'discussion_count',
+			'join_tablename'  => 'node',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (node_id) WHERE node.node_type_id = "Category" OR node.node_type_id = "Forum" ',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		// Note: We join the 'forum' table because 'node' does not include reply counts.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'message_count',
+			'join_tablename'  => 'node',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (node_id) WHERE node.node_type_id = "Category" OR node.node_type_id = "Forum" ',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'node',
+			'from_fieldname' => 'title',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		// 'node_name' only has slug for explictly named forums
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'node_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'node',
+			'from_fieldname' => 'display_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = Category or Forum = Forum, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'node',
+			'from_fieldname'  => 'node_type_id',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Unlocked = 1 or Locked = 0, Stored in postmeta)
+		// Note: We join the 'forum' table because 'node' does not include forum status.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'allow_posting',
+			'join_tablename'  => 'node',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (node_id) WHERE node.node_type_id = "Category" OR node.node_type_id = "Forum" ',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_watch',
+			'from_fieldname'  => 'node_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_watch',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'thread_id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'reply_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'reply_count',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'node_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'username',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic content.
+		// Note: We join the 'post' table because 'thread' table does not include content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'message',
+			'join_tablename'  => 'thread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'ON thread.first_post_id = post.post_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic status (Visible or Deleted)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'discussion_state',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Topic status (Open = 1 or Closed = 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'discussion_open',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'node_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'last_post_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'last_post_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'last_post_date',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * XenForo Forums do not support topic tags out of the box
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread_watch',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread_watch',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'post',
+			'from_fieldname' => 'post_id',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Join the 'thread' table to exclude topics from being imported as replies
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'thread_id',
+			'join_tablename'  => 'post',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (thread_id) WHERE thread.first_post_id != post.post_id',
+			'to_type'         => 'reply'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply status (Visible or Deleted)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'message_state',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'username',
+			'to_type'         => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous  (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'message',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'thread_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'post_date',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+/*		// User password.
+		// Note: We join the 'user_authenticate' table because 'user' does not include password.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_authenticate',
+			'from_fieldname'  => 'data',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_converter_password'
+		);
+
+		// Store old User password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old User Salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   =>   ''
+		);
+*/
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_class',
+			'default'        => 'XenForo'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		// Note: We join the 'user_profile' table because 'user' does not include user homepage.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'homepage',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'register_date',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// Store Custom Title (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'custom_title',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xenforo_user_custom_title'
+		);
+
+		// Store Status (Stored in usermeta)
+		// Note: We join the 'user_profile' table because 'user' does not include user custom XenForo field user status.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'status',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xenforo_user_status'
+		);
+
+		// Store Signature (Stored in usermeta)
+		// Note: We join the 'user_profile' table because 'user' does not include user custom XenForo field user signature.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'signature',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_fieldname'    => '_bbp_xenforo_user_sig',
+			'to_type'         => 'user',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		// Note: We join the 'user_profile' table because 'user' does not include user custom XenForo field user location.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'location',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xenforo_user_location'
+		);
+
+		// Store Occupation (Stored in usermeta)
+		// Note: We join the 'user_profile' table because 'user' does not include user custom XenForo field user occupation.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'occupation',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xenforo_user_occupation'
+		);
+
+		// Store About (Stored in usermeta)
+		// Note: We join the 'user_profile' table because 'user' does not include user custom XenForo field user about.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user_profile',
+			'from_fieldname'  => 'about',
+			'join_tablename'  => 'user',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id)',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_xenforo_user_about',
+			'callback_method' => 'callback_html'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function translate_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		switch( $pass_array['hashFunc'] ) {
+			case 'sha256':
+				return ( $pass_array['hash'] == hash( 'sha256', hash( 'sha256', $password ) . $pass_array['salt'] ) );
+			case 'sha1':
+				return ( $pass_array['hash'] == sha1( sha1( $password ) . $pass_array['salt'] ) );
+		}
+	}
+
+	/**
+	 * Translate the forum type from XenForo Capitalised case to WordPress's non-capatilise case strings.
+	 *
+	 * @param int $status XenForo numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 1 ) {
+		switch ( $status ) {
+			case 'Category' :
+				$status = 'category';
+				break;
+
+			case 'Forum' :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from XenForo numerics to WordPress's strings.
+	 *
+	 * @param int $status XenForo numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1 :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from XenForo to WordPress' strings.
+	 *
+	 * @param int $status XenForo post status
+	 * @return string WordPress safe
+	 */
+	public function callback_status( $status = 1 ) {
+		switch ( $status ) {
+			case 'deleted' :
+				$status = 'pending'; // bbp_get_pending_status_id()
+				break;
+
+			case 'visible'  :
+			default :
+				$status = 'publish'; // bbp_get_public_status_id()
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic status from XenForo numerics to WordPress's strings.
+	 *
+	 * @param int $status XenForo numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1 :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from XenForo numerics to WordPress's strings.
+	 *
+	 * @param int $status XenForo numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // XenForo Sticky 'sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // XenForo Normal Topic 'sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count XenForo reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips Xenforo custom HTML first from $field before parsing $field to parser.php
+		$xenforo_markup = $field;
+		$xenforo_markup = html_entity_decode( $xenforo_markup );
+
+		// Replace '[QUOTE]' with '<blockquote>'
+		$xenforo_markup = preg_replace( '/\[QUOTE\]/', '<blockquote>', $xenforo_markup );
+		// Replace '[/QUOTE]' with '</blockquote>'
+		$xenforo_markup = preg_replace( '/\[\/QUOTE\]/', '</blockquote>', $xenforo_markup );
+		// Replace '[QUOTE=User Name($1)]' with '<em>@$1 wrote:</em><blockquote>"
+		$xenforo_markup = preg_replace( '/\[quote=\"(.*?)\,\spost\:\s(.*?)\,\s\member\:\s(.*?)\"\](.*?)\[\/quote\]/', '<em>@$1 wrote:</em><blockquote>', $xenforo_markup );
+		// Replace '[/quote]' with '</blockquote>'
+		$xenforo_markup = preg_replace( '/\[\/quote\]/', '</blockquote>', $xenforo_markup );
+
+		// Replace '[media=youtube]$1[/media]' with '$1"
+		$xenforo_markup = preg_replace( '/\[media\=youtube\](.*?)\[\/media\]/', 'https://youtu.be/$1', $xenforo_markup );
+		// Replace '[media=dailymotion]$1[/media]' with '$1"
+		$xenforo_markup = preg_replace( '/\[media\=dailymotion\](.*?)\[\/media\]/', 'https://www.dailymotion.com/video/$1', $xenforo_markup );
+		// Replace '[media=vimeo]$1[/media]' with '$1"
+		$xenforo_markup = preg_replace( '/\[media\=vimeo\](.*?)\[\/media\]/', 'https://vimeo.com/$1', $xenforo_markup );
+
+		// Now that Xenforo custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $xenforo_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/bbPress1.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/bbPress1.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/bbPress1.php	(revision 7162)
@@ -0,0 +1,759 @@
+<?php
+
+/**
+ * bbPress 1.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * bbPress 1.1 Converter
+ *
+ * @since 2.1.0 bbPress (r3816)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/bbpress-1-x-buddypress-group-forums
+ */
+class bbPress1 extends BBP_Converter_Base {
+
+	/**
+	 * Main constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_parent',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'topics',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'posts',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'   => 'forums',
+			'from_fieldname'   => 'forum_slug',
+			'to_type'          => 'forum',
+			'to_fieldname'     => 'post_name',
+			'callback_method'  => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'   => 'forums',
+			'from_fieldname'   => 'forum_desc',
+			'to_type'          => 'forum',
+			'to_fieldname'     => 'post_content',
+			'callback_method'  => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (bbPress v1.x Forum > 0 or Category = 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'meta',
+			'from_fieldname'  => 'meta_value',
+			'join_tablename'  => 'forums',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'ON meta.object_id = forums.forum_id AND meta.meta_key = "forum_is_category"',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		/**
+		 * bbPress 1.x Forums do not support forum subscriptions
+		 */
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_posts',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_poster',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_slug',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'topics' table does not include content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_text',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_position IN (0,1)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic status (Spam, Trash or Publish, bbPress v1.x publish = 0, trash = 1 & spam = 2)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_status',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_position IN (0,1)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Topic status (Publish or Closed to new replies)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_open',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic author ip (Stored in postmeta)
+		// Note: We join the 'posts' table because 'topics' table does not include author ip.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_position IN (0,1)',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_start_time',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_start_time',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_time',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_time',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_modified_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_time',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'term_relationships',
+			'from_fieldname'  => 'object_id',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'term_taxonomy',
+			'from_fieldname'  => 'term_taxonomy_id',
+			'join_tablename'  => 'term_relationships',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_taxonomy_id)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'term_taxonomy',
+			'from_fieldname'  => 'description',
+			'join_tablename'  => 'term_relationships',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_taxonomy_id)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'description',
+			'callback_method' => 'callback_html'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'terms',
+			'from_fieldname'  => 'name',
+			'join_tablename'  => 'term_taxonomy',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_id) WHERE term_taxonomy.taxonomy = "bb_topic_tag"',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'terms',
+			'from_fieldname'  => 'slug',
+			'join_tablename'  => 'term_taxonomy',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_id) WHERE term_taxonomy.taxonomy = "bb_topic_tag"',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'term_relationships',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Join the 'term_taxonomy' table to link 'terms' 'term_relationships' tables
+		$this->field_map[] = array(
+			'from_tablename'  => 'term_taxonomy',
+			'from_fieldname'  => 'term_taxonomy_id',
+			'join_tablename'  => 'term_relationships',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_taxonomy_id)',
+			'to_type'         => 'topic_subscriptions'
+		);
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'terms',
+			'from_fieldname'  => 'name',
+			'join_tablename'  => 'term_taxonomy',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (term_id) WHERE term_taxonomy.taxonomy = "bb_subscribe"',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions',
+			'callback_method' => 'callback_topic_subscriptions'
+		);
+
+		/** Favorites Section *************************************************/
+
+		// Favorited topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'usermeta',
+			'from_fieldname'  => 'meta_value',
+			'from_expression' => 'WHERE usermeta.meta_key = "bb_favorites"',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => '_bbp_favorites'
+		);
+
+		// Favorited user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'usermeta',
+			'from_fieldname'  => 'user_id',
+			'from_expression' => 'WHERE usermeta.meta_key = "bb_favorites"',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		// Note: We join the 'topics' table to limit the replies section to only import replies
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_id',
+			'join_tablename'  => 'posts',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_position NOT IN (0,1)',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply status (Spam, Trash or Publish, bbPress v1.x publish = 0, trash = 1 & spam = 2)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_status',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_status',
+			'callback_method' => 'callback_status'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_text',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply order.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_position',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'menu_order'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'post_time',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_date'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'post_time',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_date_gmt'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'post_time',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_modified'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'post_time',
+			'to_type'        => 'reply',
+			'to_fieldname'   => 'post_modified_gmt'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'ID',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_pass',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_password'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_login',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_nicename',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_url',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_registered',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_registered'
+		);
+
+		// User status.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_status',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_status'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'display_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * Translate the post status from bbPress 1's numerics to WordPress's
+	 * strings.
+	 *
+	 * @param int $status bbPress 1.x numeric post status
+	 * @return string WordPress safe
+	 */
+	public function callback_status( $status = 0 ) {
+		switch ( $status ) {
+			case 2 :
+				$status = 'spam';    // bbp_get_spam_status_id()
+				break;
+
+			case 1 :
+				$status = 'trash';   // bbp_get_trash_status_id()
+				break;
+
+			case 0  :
+			default :
+				$status = 'publish'; // bbp_get_public_status_id()
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum type from bbPress 1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status bbPress 1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == 1 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic status from bbPress 1's numerics to WordPress's
+	 * strings.
+	 *
+	 * @param int $topic_status bbPress 1.x numeric status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $topic_status = 1 ) {
+		switch ( $topic_status ) {
+			case 0 :
+				$topic_status = 'closed';  // bbp_get_closed_status_id()
+				break;
+
+			case 1 :
+				default :
+				$topic_status = 'publish'; // bbp_get_public_status_id()
+				break;
+		}
+		return $topic_status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from bbPress 1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status bbPress 1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 2 :
+				$status = 'super-sticky'; // bbPress Super Sticky 'topic_sticky = 2'
+				break;
+
+			case 1 :
+				$status = 'sticky';       // bbPress Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // bbPress Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count bbPress 1.x topic and reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This method is to save the salt and password together. That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return false;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		return false;
+	}
+
+	/**
+	 * This callback strips `topic-` from topic subscriptions taxonomy
+	 *
+	 * @since 2.6.0 bbPress (r5572)
+	 *
+	 * @param string $field Topic ID
+	 * @return integer WordPress safe
+	 */
+	protected function callback_topic_subscriptions( $field ) {
+
+		// Replace 'topic-' with '' so that only the original topic ID remains
+		$field = absint( (int) preg_replace( '/(topic-)(\d+)/', '$2', $field ) );
+		return $field;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/e107v1.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/e107v1.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/e107v1.php	(revision 7162)
@@ -0,0 +1,637 @@
+<?php
+
+/**
+ * bbPress e107 1.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of e107 v1.x Forum converter.
+ *
+ * @since 2.6.0 bbPress (r5352)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/e107
+ */
+class e107v1 extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'forum_parent',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_threads',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_replies',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_threads',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_replies',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'forum_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'forum_description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forum_order',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum > 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'forum_parent',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_id',
+			'from_expression' => 'WHERE thread_parent = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_total_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_total_replies',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		// Note: Uses a custom callback to transform user id from '1.Administrator e107v1' to numeric user id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_user',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_e107v1_userid'
+		);
+
+		// Topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_thread',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum_t',
+			'from_fieldname' => 'thread_name',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_name',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic status (Open or Closed, e107 v1.x open = 1 & closed = 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_active',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_s',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * e107 v1.x Forums do not support topic tags out of the box
+		 */
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_id',
+			'from_expression' => 'WHERE thread_parent != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_forum_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_parent',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author.
+		// Note: Uses a custom callback to transform user id from '1.Administrator e107v1' to numeric user id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_user',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_e107v1_userid'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_thread',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_parent',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum_t',
+			'from_fieldname'  => 'thread_datestamp',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_id',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'user_password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'e107v1'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_loginname',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User nice name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_loginname',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_nicename'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'user_join',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User display name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'user_name',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'display_name'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'user_signature',
+			'to_fieldname'    => '_bbp_e107v1_user_sig',
+			'to_type'         => 'user',
+			'callback_method' => 'callback_html'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together. That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ). $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from e107 v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status e107 v1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == 0 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the post status from e107 v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status e107 v1.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1 :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from e107 v1.x numerics to WordPress's strings.
+	 *
+	 * @param int $status e107 v1.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 2 :
+				$status = 'super-sticky'; // e107 Announcement Sticky 'thread_s = 2'
+				break;
+
+			case 1 :
+				$status = 'sticky';       // e107 Sticky 'thread_s = 1'
+				break;
+
+			case 0 :
+			default :
+				$status = 'normal';       // e107 normal topic 'thread_s = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic/reply count.
+	 *
+	 * @param int $count e107 v1.x topic/reply counts
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * Override the `callback_user` function in 'converter.php' for custom e107v1 imports
+	 *
+	 * A mini cache system to reduce database calls to user ID's
+	 *
+	 * @param string $field
+	 * @return string
+	 */
+	protected function callback_e107v1_userid( $field ) {
+
+		// Strip only the user id from the topic and reply authors
+		$field = preg_replace( '/(\d+?)+\.[\S\s]+/', '$1', $field );
+
+		if ( ! isset( $this->map_userid[ $field ] ) ) {
+			if ( ! empty( $this->sync_table ) ) {
+				$row = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) );
+			} else {
+				$row = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT user_id AS value_id FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) );
+			}
+
+			if ( ! is_null( $row ) ) {
+				$this->map_userid[ $field ] = $row->value_id;
+			} else {
+				if ( true === $this->convert_users ) {
+					$this->map_userid[ $field ] = 0;
+				} else {
+					$this->map_userid[ $field ] = $field;
+				}
+			}
+		}
+		return $this->map_userid[ $field ];
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips custom e107v1 'magic_url' and 'bbcode_uid' first from $field before parsing $field to parser.php
+		$e107v1_markup = $field;
+		$e107v1_markup = html_entity_decode( $e107v1_markup );
+
+		// Replace '[blockquote]' with '<blockquote>'
+		$e107v1_markup = preg_replace( '/\[blockquote\]/',   '<blockquote>',  $e107v1_markup );
+		// Replace '[/blockquote]' with '</blockquote>'
+		$e107v1_markup = preg_replace( '/\[\/blockquote\]/', '</blockquote>', $e107v1_markup );
+
+		// Replace '[quote$1=$2]' with '<em>$2 wrote:</em><blockquote>"
+		$e107v1_markup = preg_replace( '/\[quote(.*?)=(.*?)\]/', '<em>@$2 wrote:</em><blockquote>', $e107v1_markup );
+		// Replace '[/quote$1]' with '</blockquote>"
+		$e107v1_markup = preg_replace( '/\[\/quote(.*?)\]/' ,    '</blockquote>',                   $e107v1_markup );
+
+		// Remove '[justify]'
+		$e107v1_markup = preg_replace( '/\[justify\]/',   '', $e107v1_markup );
+		// Remove '[/justify]'
+		$e107v1_markup = preg_replace( '/\[\/justify\]/', '', $e107v1_markup );
+
+		// Replace '[link=(https|http)://$2]$3[/link]' with '<a href="$1://$2">$3</a>'
+		$e107v1_markup = preg_replace( '/\[link\=(https|http)\:\/\/(.*?)\](.*?)\[\/link\]/i',  '<a href="$1://$2">$3</a>',  $e107v1_markup );
+
+		// Replace '[list=(decimal|lower-roman|upper-roman|lower-alpha|upper-alpha)]' with '[list]'
+		$e107v1_markup = preg_replace( '/\[list\=(decimal|lower-roman|upper-roman|lower-alpha|upper-alpha)\]/i',  '[list]',  $e107v1_markup );
+
+		// Replace '[youtube]$1[/youtube]' with '$1"
+		$e107v1_markup = preg_replace( '/\[youtube\](.*?)\[\/youtube\]/', 'https://youtu.be/$1', $e107v1_markup );
+
+		// Now that e107v1 custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $e107v1_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/index.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/admin/converters/phpBB.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/phpBB.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/phpBB.php	(revision 7162)
@@ -0,0 +1,1027 @@
+<?php
+
+/**
+ * bbPress phpBB 3.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of phpBB v3 Converter.
+ *
+ * @since 2.3.0 bbPress (r4689)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/phpbb
+ */
+class phpBB extends BBP_Converter_Base {
+
+	/**
+	 * Main Constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'parent_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_topics_approved',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_posts_approved',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_topics_approved',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_posts_approved',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'forum_name',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_name',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_desc',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forums',
+			'from_fieldname' => 'left_id',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = 0 or Forum = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_type',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Unlocked = 0 or Locked = 1, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums',
+			'from_fieldname'  => 'forum_status',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_status',
+			'callback_method' => 'callback_forum_status'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date('Y-m-d H:i:s')
+		);
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date('Y-m-d H:i:s')
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_watch',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forums_watch',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_id',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_posts_approved',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_posts_approved',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_poster',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_first_poster_name',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_poster',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_ip',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_id = topics.topic_first_post_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic content.
+		// Note: We join the 'posts' table because 'topics' does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_text',
+			'join_tablename'  => 'topics',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_id = topics.topic_first_post_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'topics',
+			'from_fieldname' => 'topic_title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_status',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_type',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_last_post_time',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		/**
+		 * phpBB Forums do not support topic tags
+		 */
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_watch',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics_watch',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Favorites Section *************************************************/
+
+		// Favorited topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bookmarks',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => '_bbp_favorites'
+		);
+
+		// Favorited user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'bookmarks',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'favorites',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'post_id',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_old_reply_id'
+		);
+
+		// Setup reply section table joins
+		$this->field_map[] = array(
+			'from_tablename'  => 'topics',
+			'from_fieldname'  => 'topic_id',
+			'join_tablename'  => 'posts',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (topic_id) WHERE posts.post_id != topics.topic_first_post_id',
+			'to_type'         => 'reply'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'forum_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'posts',
+			'from_fieldname' => 'poster_ip',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_username',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'poster_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_text',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'topic_id',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'posts',
+			'from_fieldname'  => 'post_time',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		// Don't import users with id 2, these are phpBB bot/crawler accounts
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'user_id',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'user_password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_form_salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'phpBB'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename'  => 'profile_fields_data',
+			'from_fieldname'  => 'pf_phpbb_website',
+			'join_tablename'  => 'users',
+			'join_type'       => 'LEFT',
+			'join_expression' => 'USING (user_id) WHERE users.user_type !=2',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'users',
+			'from_fieldname'  => 'user_regdate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User AOL/AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_aol',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_yim'
+		);
+
+		// Store ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_icq'
+		);
+
+		// Store MSN/WLM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_wlm',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_msnm'
+		);
+
+		// Store Facebook (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_facebook',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_facebook'
+		);
+
+		// Store Google+ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_googleplus',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_googleplus'
+		);
+
+		// Store Skype (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_skype',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_skype'
+		);
+
+		// Store Twitter (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_twitter',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_twitter'
+		);
+
+		// Store Youtube (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_youtube',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_youtube'
+		);
+
+		// Store Jabber
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_jabber',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_jabber'
+		);
+
+		// Store Occupation (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_occupation',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_occ'
+		);
+
+		// Store Interests (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_interests',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_interests'
+		);
+
+		// Store Signature (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_sig',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_sig',
+			'callback_method' => 'callback_html'
+		);
+
+		// Store Location (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'profile_fields_data',
+			'from_fieldname' => 'pf_phpbb_location',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_from'
+		);
+
+		// Store Avatar Filename (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'users',
+			'from_fieldname' => 'user_avatar',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_phpbb_user_avatar'
+		);
+
+		// Store old role (Stored in usermeta)
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value.
+	 */
+	public function callback_savepass( $field, $row ) {
+		return array(
+			'hash' => $field,
+			'salt' => $row['user_form_salt']
+		);
+	}
+
+	/**
+	 * Check for correct password
+	 *
+	 * @param string $password The password in plain text
+	 * @param string $hash The stored password hash
+	 *
+	 * @link Original source for password functions http://openwall.com/phpass/
+	 * @link phpass is now included in WP Core https://core.trac.wordpress.org/browser/trunk/wp-includes/class-phpass.php
+	 *
+	 * @return bool Returns true if the password is correct, false if not.
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+		$pass_array = unserialize( $serialized_pass );
+		if ( strlen( $pass_array['hash'] ) == 34 ) {
+			return ( $this->_hash_crypt_private( $password, $pass_array['hash'], $itoa64 ) === $pass_array['hash'] ) ? true : false;
+		}
+
+		return ( md5( $password ) === $pass_array['hash'] ) ? true : false;
+	}
+
+	/**
+	 * The crypt function/replacement
+	 */
+	private function _hash_crypt_private( $password, $setting, &$itoa64 ) {
+		$output = '*';
+
+		// Check for correct hash
+		if ( substr( $setting, 0, 3 ) != '$H$' ) {
+			return $output;
+		}
+
+		$count_log2 = strpos( $itoa64, $setting[3] );
+
+		if ( $count_log2 < 7 || $count_log2 > 30 ) {
+			return $output;
+		}
+
+		$count = 1 << $count_log2;
+		$salt  = substr( $setting, 4, 8 );
+
+		if ( strlen( $salt ) != 8 ) {
+			return $output;
+		}
+
+		/**
+		 * We're kind of forced to use MD5 here since it's the only
+		 * cryptographic primitive available in all versions of PHP
+		 * currently in use.  To implement our own low-level crypto
+		 * in PHP would result in much worse performance and
+		 * consequently in lower iteration counts and hashes that are
+		 * quicker to crack (by non-PHP code).
+		 */
+		if ( floatval( phpversion() ) >= 5 ) {
+			$hash = md5( $salt . $password, true );
+			do {
+				$hash = md5( $hash . $password, true );
+			}
+			while ( --$count );
+		} else {
+			$hash = pack( 'H*', md5( $salt . $password ) );
+			do {
+				$hash = pack( 'H*', md5( $hash . $password ) );
+			}
+			while ( --$count );
+		}
+
+		$output = substr($setting, 0, 12);
+		$output .= $this->_hash_encode64($hash, 16, $itoa64);
+
+		return $output;
+	}
+
+	/**
+	 * Encode hash
+	 */
+	private function _hash_encode64( $input, $count, &$itoa64 ) {
+		$output = '';
+		$i = 0;
+
+		do {
+			$value = ord( $input[ $i++ ] );
+			$output .= $itoa64[ $value & 0x3f ];
+
+			if ($i < $count) {
+				$value |= ord( $input[ $i ] ) << 8;
+			}
+
+			$output .= $itoa64[( $value >> 6 ) & 0x3f];
+
+			if ( $i++ >= $count ) {
+				break;
+			}
+
+			if ( $i < $count ) {
+				$value |= ord( $input[ $i ] ) << 16;
+			}
+
+			$output .= $itoa64[( $value >> 12 ) & 0x3f];
+
+			if ( $i++ >= $count ) {
+				break;
+			}
+
+			$output .= $itoa64[($value >> 18) & 0x3f];
+		} while ( $i < $count );
+
+		return $output;
+	}
+
+	/**
+	 * Translate the forum type from phpBB v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status phpBB v3.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'category';
+				break;
+
+			case 1  :
+			default :
+				$status = 'forum';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the forum status from phpBB v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status phpBB v3.x numeric forum status
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0  :
+			default :
+				$status = 'open';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic status from phpBB v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status phpBB v3.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'closed';
+				break;
+
+			case 0 :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from phpBB 3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status phpBB 3.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 3 :
+				$status = 'super-sticky'; // phpBB Global Sticky 'topic_type = 3'
+				break;
+
+			case 2 :
+				$status = 'super-sticky'; // phpBB Announcement Sticky 'topic_type = 2'
+				break;
+
+			case 1 :
+				$status = 'sticky';       // phpBB Sticky 'topic_type = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // phpBB normal topic 'topic_type = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count phpBB v3.x reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips custom phpBB 'magic_url' and 'bbcode_uid' first from $field before parsing $field to parser.php
+		$phpbb_uid = $field;
+		$phpbb_uid = html_entity_decode( $phpbb_uid );
+
+		// Replace '[b:XXXXXXX]' with '<strong>'
+		$phpbb_uid = preg_replace( '/\[b:(.*?)\]/',   '<strong>',  $phpbb_uid );
+		// Replace '[/b:XXXXXXX]' with '</strong>'
+		$phpbb_uid = preg_replace( '/\[\/b:(.*?)\]/', '</strong>', $phpbb_uid );
+
+		// Replace '[i:XXXXXXX]' with '<em>'
+		$phpbb_uid = preg_replace( '/\[i:(.*?)\]/',   '<em>',      $phpbb_uid );
+		// Replace '[/i:XXXXXXX]' with '</em>'
+		$phpbb_uid = preg_replace( '/\[\/i:(.*?)\]/', '</em>',     $phpbb_uid );
+
+		// Replace '[u:XXXXXXX]' with '<u>'
+		$phpbb_uid = preg_replace( '/\[u:(.*?)\]/',   '<u>',       $phpbb_uid );
+		// Replace '[/u:XXXXXXX]' with '</u>'
+		$phpbb_uid = preg_replace( '/\[\/u:(.*?)\]/', '</u>',      $phpbb_uid );
+
+		// Replace '[quote:XXXXXXX]' with '<blockquote>'
+		$phpbb_uid = preg_replace( '/\[quote:(.*?)\]/',          '<blockquote>',                   $phpbb_uid );
+		// Replace '[quote="$1"]' with '<em>$1 wrote:</em><blockquote>"
+		$phpbb_uid = preg_replace( '/\[quote="(.*?)":(.*?)\]/', '<em>@$1 wrote:</em><blockquote>', $phpbb_uid );
+		// Replace '[/quote:XXXXXXX]' with '</blockquote>'
+		$phpbb_uid = preg_replace( '/\[\/quote:(.*?)\]/',       '</blockquote>',                   $phpbb_uid );
+
+		// Replace '[img:XXXXXXX]' with '<img src="'
+		$phpbb_uid = preg_replace( '/\[img:(.*?)\]/',   '<img src="', $phpbb_uid );
+		// Replace '[/img:XXXXXXX]' with ' alt="">'
+		$phpbb_uid = preg_replace( '/\[\/img:(.*?)\]/', '" alt="">',  $phpbb_uid );
+
+		// Replace '<!-- s$1 --><img src=\"{SMILIES_PATH}$2 -->' with '$1'
+		$phpbb_uid = preg_replace( '/<!-- s(.*?) --><img src=\"{SMILIES_PATH}(.*?)-->/', '$1', $phpbb_uid );
+
+		// Replace '<!-- m --><a class="postlink" href="$1">$1</a><!-- m -->' with '$1'
+		$phpbb_uid = preg_replace( '/\<!-- m --\>\<a class="postlink" href="([^\[]+?)"\>([^\[]+?)\<\/a\>\<!-- m --\>/', '$1', $phpbb_uid );
+
+		// Replace '[url:XXXXXXX]$1[/url:XXXXXXX]' with '<a href="http://$1">$1</a>'
+		$phpbb_uid = preg_replace( '/\[url:(?:[^\]]+)\]([^\[]+?)\[\/url:(?:[^\]]+)\]/',       '<a href="http://$1">$1</a>',  $phpbb_uid );
+		// Replace '[url=http://$1:XXXXXXX]$3[/url:XXXXXXX]' with '<a href="http://$1">$3</a>'
+		$phpbb_uid = preg_replace( '/\[url\=http\:\/\/(.*?)\:(.*?)\](.*?)\[\/url:(.*?)\]/i',  '<a href="http://$1">$3</a>',  $phpbb_uid );
+		// Replace '[url=https://$1:XXXXXXX]$3[/url:XXXXXXX]' with '<a href="http://$1">$3</a>'
+		$phpbb_uid = preg_replace( '/\[url\=https\:\/\/(.*?)\:(.*?)\](.*?)\[\/url:(.*?)\]/i', '<a href="https://$1">$3</a>', $phpbb_uid );
+
+		// Replace '[email:XXXXXXX]' with '<a href="mailto:$2">$2</a>'
+		$phpbb_uid = preg_replace( '/\[email:(.*?)\](.*?)\[\/email:(.*?)\]/', '<a href="mailto:$2">$2</a>', $phpbb_uid );
+		// Replace '<!-- e -->no.one@domain.adr<!-- e -->' with '$1'
+		$phpbb_uid = preg_replace( '/\<!-- e --\>(.*?)\<!-- e --\>/', '$1', $phpbb_uid );
+
+		// Replace '[code:XXXXXXX]' with '<pre><code>'
+		$phpbb_uid = preg_replace( '/\[code:(.*?)\]/',   '<pre><code>',   $phpbb_uid );
+		// Replace '[/code:XXXXXXX]' with '</code></pre>'
+		$phpbb_uid = preg_replace( '/\[\/code:(.*?)\]/', '</code></pre>', $phpbb_uid );
+
+		// Replace '[color=$1:XXXXXXXX]' with '<span style="color:$1">'
+		$phpbb_uid = preg_replace( '/\[color=(.*?)\:(.*?)\]/', '<span style="color: $1">', $phpbb_uid );
+		// Replace '[/color:XXXXXXX]' with '</span>'
+		$phpbb_uid = preg_replace( '/\[\/color:(.*?)\]/',      '</span>',                  $phpbb_uid );
+
+		// Replace '[size=$1:XXXXXXXX]' with '<span style="font-size:$1%;">$3</span>'
+		$phpbb_uid = preg_replace( '/\[size=(.*?):(.*?)\]/', '<span style="font-size:$1%;">', $phpbb_uid );
+		// Replace '[/size:XXXXXXX]' with ''
+		$phpbb_uid = preg_replace( '/\[\/size:(.*?)\]/',     '</span>',                       $phpbb_uid );
+
+		// Replace '[list:XXXXXXX]' with '<ul>'
+		$phpbb_uid = preg_replace( '/\[list:(.*?)\]/',     '<ul>',          $phpbb_uid );
+		// Replace '[list=a:XXXXXXX]' with '<ol type="a">'
+		$phpbb_uid = preg_replace( '/\[list=a:(.*?)\]/',   '<ol type="a">', $phpbb_uid );
+		// Replace '[list=1:XXXXXXX]' with '<ol>'
+		$phpbb_uid = preg_replace( '/\[list=1:(.*?)\]/',   '<ol>',          $phpbb_uid );
+		// Replace '[*:XXXXXXX]' with '<li>'
+		$phpbb_uid = preg_replace( '/\[\*:(.*?)\]/',       '<li>',          $phpbb_uid );
+		// Replace '[/*:m:XXXXXXX]' with '</li>'
+		$phpbb_uid = preg_replace( '/\[\/\*:m:(.*?)\]/',   '</li>',         $phpbb_uid );
+		// Replace '[/list:u:XXXXXXX]' with '</ul>'
+		$phpbb_uid = preg_replace( '/\[\/list:u:(.*?)\]/', '</ul>',         $phpbb_uid );
+		// Replace '[/list:o:XXXXXXX]' with '</ol>'
+		$phpbb_uid = preg_replace( '/\[\/list:o:(.*?)\]/', '</ol>',         $phpbb_uid );
+
+		// Now that phpBB's 'magic_url' and 'bbcode_uid' have been stripped put the cleaned HTML back in $field
+		$field = $phpbb_uid;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/vBulletin.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/vBulletin.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/vBulletin.php	(revision 7162)
@@ -0,0 +1,756 @@
+<?php
+
+/**
+ * bbPress vBulletin 4.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of vBulletin v4.x Converter.
+ *
+ * @since 2.3.0 bbPress (r4724)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/vbulletin
+ */
+class vBulletin extends BBP_Converter_Base {
+
+	/**
+	 * Main constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forumid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'parentid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'threadcount',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'replycount',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'threadcount',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'title',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'title_clean',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'displayorder',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = -1 or Forum > 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'parentid',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribeforum',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribeforum',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'threadid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postuserid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postusername',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postuserid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'post' table because 'thread' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'ipaddress',
+			'join_tablename'  => 'thread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (threadid) WHERE post.parentid = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic content.
+		// Note: We join the 'post' table because 'thread' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'pagetext',
+			'join_tablename'  => 'thread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (threadid) WHERE post.parentid = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'open',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'lastpost',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagcontent',
+			'from_fieldname'  => 'contentid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagcontent',
+			'from_fieldname'  => 'tagid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tagtext',
+			'join_tablename'  => 'tagcontent',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tagid)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tagtext',
+			'join_tablename'  => 'tagcontent',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tagid)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribethread',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribethread',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'postid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'from_expression' => 'WHERE parentid != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'post',
+			'from_fieldname' => 'ipaddress',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'username',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'pagetext',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[]	 = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'userid',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_salt'
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'vBulletin'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'homepage',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'joindate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin_user_yim'
+		);
+
+		// User ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin_user_icq'
+		);
+
+		// User MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin_user_msn'
+		);
+
+		// User Skype (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'skype',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin_user_skype'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash' => $field, 'salt' => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 *
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ) . $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from vBulletin v4.x numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v4.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == -1 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from vBulletin v4.x numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v4.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 2 :
+				$status = 'super-sticky'; // vBulletin Super Sticky 'sticky = 2'
+				break;
+
+			case 1 :
+				$status = 'sticky';       // vBulletin Sticky 'sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // vBulletin Normal Topic 'sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count vBulletin v4.x reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * Translate the post status from vBulletin numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v4.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips vBulletin custom HTML first from $field before parsing $field to parser.php
+		$vbulletin_markup = $field;
+		$vbulletin_markup = html_entity_decode( $vbulletin_markup );
+
+		// Replace '[QUOTE]' with '<blockquote>'
+		$vbulletin_markup = preg_replace( '/\[QUOTE\]/', '<blockquote>', $vbulletin_markup );
+		// Replace '[QUOTE=User Name($1);PostID($2)]' with '<em>@$1 $2 wrote:</em><blockquote>"
+		$vbulletin_markup = preg_replace( '/\[QUOTE=(.*?);(.*?)\]/', '<em>@$1 $2 wrote:</em><blockquote>', $vbulletin_markup );
+		// Replace '[/QUOTE]' with '</blockquote>'
+		$vbulletin_markup = preg_replace( '/\[\/QUOTE\]/', '</blockquote>', $vbulletin_markup );
+		// Replace '[MENTION=###($1)]User Name($2)[/MENTION]' with '@$2"
+		$vbulletin_markup = preg_replace( '/\[MENTION=(.*?)\](.*?)\[\/MENTION\]/', '@$2', $vbulletin_markup );
+
+		// Replace '[video=youtube;$1]$2[/video]' with '$2"
+		$vbulletin_markup = preg_replace( '/\[video\=youtube;(.*?)\](.*?)\[\/video\]/', '$2', $vbulletin_markup );
+		// Replace '[video=youtube_share;$1]$2[/video]' with '$2"
+		$vbulletin_markup = preg_replace( '/\[video\=youtube_share;(.*?)\](.*?)\[\/video\]/', '$2', $vbulletin_markup );
+
+		// Now that vBulletin custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $vbulletin_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/converters/vBulletin3.php
===================================================================
--- /tags/2.6.6/src/includes/admin/converters/vBulletin3.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/converters/vBulletin3.php	(revision 7162)
@@ -0,0 +1,754 @@
+<?php
+
+/**
+ * bbPress vBulletin 3.x Converter
+ *
+ * @package bbPress
+ * @subpackage Converters
+ */
+
+/**
+ * Implementation of vBulletin v3.x Converter.
+ *
+ * @since 2.5.0 bbPress (r5151)
+ *
+ * @link Codex Docs https://codex.bbpress.org/import-forums/vbulletin
+ */
+class vBulletin3 extends BBP_Converter_Base {
+
+	/**
+	 * Main constructor
+	 *
+	 */
+	public function __construct() {
+		parent::__construct();
+	}
+
+	/**
+	 * Sets up the field mappings
+	 */
+	public function setup_globals() {
+
+		// Setup smiley URL & path
+		$this->bbcode_parser_properties = array(
+			'smiley_url' => false,
+			'smiley_dir' => false
+		);
+
+		/** Forum Section *****************************************************/
+
+		// Old forum id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'forumid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_id'
+		);
+
+		// Forum parent id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'parentid',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_old_forum_parent_id'
+		);
+
+		// Forum topic count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'threadcount',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_topic_count'
+		);
+
+		// Forum reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'replycount',
+			'to_type'        => 'forum',
+			'to_fieldname'   => '_bbp_reply_count'
+		);
+
+		// Forum total topic count (Includes unpublished topics, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'threadcount',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_topic_count'
+		);
+
+		// Forum total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_total_reply_count'
+		);
+
+		// Forum title.
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'title',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Forum slug (Clean name to avoid confilcts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'title_clean',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Forum description.
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'description',
+			'to_type'         => 'forum',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_null'
+		);
+
+		// Forum display order (Starts from 1)
+		$this->field_map[] = array(
+			'from_tablename' => 'forum',
+			'from_fieldname' => 'displayorder',
+			'to_type'        => 'forum',
+			'to_fieldname'   => 'menu_order'
+		);
+
+		// Forum type (Category = -1 or Forum > 0, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'forum',
+			'from_fieldname'  => 'parentid',
+			'to_type'         => 'forum',
+			'to_fieldname'    => '_bbp_forum_type',
+			'callback_method' => 'callback_forum_type'
+		);
+
+		// Forum status (Set a default value 'open', Stored in postmeta)
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => '_bbp_status',
+			'default'      => 'open'
+		);
+
+		// Forum dates.
+		$this->field_map[] = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_date_gmt',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+		$this->field_map[]	 = array(
+			'to_type'      => 'forum',
+			'to_fieldname' => 'post_modified_gmt',
+			'default'      => date( 'Y-m-d H:i:s' )
+		);
+
+		/** Forum Subscriptions Section ***************************************/
+
+		// Subscribed forum ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribeforum',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => '_bbp_forum_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribeforum',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'forum_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Topic Section *****************************************************/
+
+		// Old topic id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'threadid',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_old_topic_id'
+		);
+
+		// Topic parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic reply count (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic total reply count (Includes unpublished replies, Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'replycount',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_total_reply_count',
+			'callback_method' => 'callback_topic_reply_count'
+		);
+
+		// Topic author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postuserid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Topic author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postusername',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_topic_author_name_id'
+		);
+
+		// Is the topic anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'postuserid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_is_topic_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Topic Author ip (Stored in postmeta)
+		// Note: We join the 'post' table because 'thread' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'ipaddress',
+			'join_tablename'  => 'thread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (threadid) WHERE post.parentid = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_author_ip'
+		);
+
+		// Topic title.
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'title',
+			'to_type'        => 'topic',
+			'to_fieldname'   => 'post_title'
+		);
+
+		// Topic slug (Clean name to avoid conflicts)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'title',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_name',
+			'callback_method' => 'callback_slug'
+		);
+
+		// Topic parent forum id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'forumid',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_forumid'
+		);
+
+		// Topic content.
+		// Note: We join the 'post' table because 'thread' table does not include topic content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'pagetext',
+			'join_tablename'  => 'thread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (threadid) WHERE post.parentid = 0',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Topic status (Open or Closed)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'open',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_closed_status_id',
+			'callback_method' => 'callback_topic_status'
+		);
+
+		// Sticky status (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'sticky',
+			'to_type'         => 'topic',
+			'to_fieldname'    => '_bbp_old_sticky_status_id',
+			'callback_method' => 'callback_sticky_status'
+		);
+
+		// Topic dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'thread',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'topic',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename' => 'thread',
+			'from_fieldname' => 'lastpost',
+			'to_type'        => 'topic',
+			'to_fieldname'   => '_bbp_last_active_time',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** Tags Section ******************************************************/
+
+		// Topic id.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagthread',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'objectid',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Taxonomy ID.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tagthread',
+			'from_fieldname'  => 'tagid',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'taxonomy'
+		);
+
+		// Term text.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tagtext',
+			'join_tablename'  => 'tagthread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tagid)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'name'
+		);
+
+		// Term slug.
+		$this->field_map[] = array(
+			'from_tablename'  => 'tag',
+			'from_fieldname'  => 'tagtext',
+			'join_tablename'  => 'tagthread',
+			'join_type'       => 'INNER',
+			'join_expression' => 'USING (tagid)',
+			'to_type'         => 'tags',
+			'to_fieldname'    => 'slug',
+			'callback_method' => 'callback_slug'
+		);
+
+		/** Topic Subscriptions Section ***************************************/
+
+		// Subscribed topic ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribethread',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => '_bbp_subscriptions'
+		);
+
+		// Subscribed user ID (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'subscribethread',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'topic_subscriptions',
+			'to_fieldname'    => 'user_id',
+			'callback_method' => 'callback_userid'
+		);
+
+		/** Reply Section *****************************************************/
+
+		// Old reply id (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'postid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_id'
+		);
+
+		// Reply parent forum id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'from_expression' => 'WHERE parentid != 0',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_forum_id',
+			'callback_method' => 'callback_topicid_to_forumid'
+		);
+
+		// Reply parent topic id (If no parent, then 0. Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_topic_id',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply author ip (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'post',
+			'from_fieldname' => 'ipaddress',
+			'to_type'        => 'reply',
+			'to_fieldname'   => '_bbp_author_ip'
+		);
+
+		// Reply author.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_author',
+			'callback_method' => 'callback_userid'
+		);
+
+		// Reply author name (Stored in postmeta as _bbp_anonymous_name)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'username',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_reply_author_name_id'
+		);
+
+		// Is the reply anonymous (Stored in postmeta)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'userid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => '_bbp_old_is_reply_anonymous_id',
+			'callback_method' => 'callback_check_anonymous'
+		);
+
+		// Reply content.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'pagetext',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_content',
+			'callback_method' => 'callback_html'
+		);
+
+		// Reply parent topic id (If no parent, then 0)
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'threadid',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_parent',
+			'callback_method' => 'callback_topicid'
+		);
+
+		// Reply dates.
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_date_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[] = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified',
+			'callback_method' => 'callback_datetime'
+		);
+		$this->field_map[]	 = array(
+			'from_tablename'  => 'post',
+			'from_fieldname'  => 'dateline',
+			'to_type'         => 'reply',
+			'to_fieldname'    => 'post_modified_gmt',
+			'callback_method' => 'callback_datetime'
+		);
+
+		/** User Section ******************************************************/
+
+		// Store old user id (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'userid',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_old_user_id'
+		);
+
+		// Store old user password (Stored in usermeta serialized with salt)
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'password',
+			'to_type'         => 'user',
+			'to_fieldname'    => '_bbp_password',
+			'callback_method' => 'callback_savepass'
+		);
+
+		// Store old user salt (This is only used for the SELECT row info for the above password save)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'salt',
+			'to_type'        => 'user',
+			'to_fieldname'   => ''
+		);
+
+		// User password verify class (Stored in usermeta for verifying password)
+		$this->field_map[] = array(
+			'to_type'      => 'user',
+			'to_fieldname' => '_bbp_class',
+			'default'      => 'vBulletin3'
+		);
+
+		// User name.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'username',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_login'
+		);
+
+		// User email.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'email',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_email'
+		);
+
+		// User homepage.
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'homepage',
+			'to_type'        => 'user',
+			'to_fieldname'   => 'user_url'
+		);
+
+		// User registered.
+		$this->field_map[] = array(
+			'from_tablename'  => 'user',
+			'from_fieldname'  => 'joindate',
+			'to_type'         => 'user',
+			'to_fieldname'    => 'user_registered',
+			'callback_method' => 'callback_datetime'
+		);
+
+		// User AIM (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'aim',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin3_user_aim'
+		);
+
+		// User Yahoo (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'yahoo',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin3_user_yim'
+		);
+
+		// User ICQ (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'icq',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin3_user_icq'
+		);
+
+		// User MSN (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'msn',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin3_user_msn'
+		);
+
+		// User Skype (Stored in usermeta)
+		$this->field_map[] = array(
+			'from_tablename' => 'user',
+			'from_fieldname' => 'skype',
+			'to_type'        => 'user',
+			'to_fieldname'   => '_bbp_vbulletin3_user_skype'
+		);
+	}
+
+	/**
+	 * This method allows us to indicates what is or is not converted for each
+	 * converter.
+	 */
+	public function info() {
+		return '';
+	}
+
+
+	/**
+	 * This method is to save the salt and password together.  That
+	 * way when we authenticate it we can get it out of the database
+	 * as one value. Array values are auto sanitized by WordPress.
+	 */
+	public function callback_savepass( $field, $row ) {
+		$pass_array = array( 'hash'	 => $field, 'salt'	 => $row['salt'] );
+		return $pass_array;
+	}
+
+	/**
+	 * This method is to take the pass out of the database and compare
+	 * to a pass the user has typed in.
+	 *
+	 * vBulletin passwords do not work. Maybe use the below plugin's approach?
+	 *
+	 * @link https://wordpress.org/extend/plugins/vb-user-copy/
+	 * @link https://plugins.trac.wordpress.org/browser/vb-user-copy/trunk/vb_user_copy.php
+	 */
+	public function authenticate_pass( $password, $serialized_pass ) {
+		$pass_array = unserialize( $serialized_pass );
+		return ( $pass_array['hash'] == md5( md5( $password ) . $pass_array['salt'] ) );
+	}
+
+	/**
+	 * Translate the forum type from vBulletin v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v3.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_forum_type( $status = 0 ) {
+		if ( $status == -1 ) {
+			$status = 'category';
+		} else {
+			$status = 'forum';
+		}
+		return $status;
+	}
+
+	/**
+	 * Translate the topic sticky status type from vBulletin v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v3.x numeric forum type
+	 * @return string WordPress safe
+	 */
+	public function callback_sticky_status( $status = 0 ) {
+		switch ( $status ) {
+			case 1 :
+				$status = 'sticky';       // vBulletin Sticky 'topic_sticky = 1'
+				break;
+
+			case 0  :
+			default :
+				$status = 'normal';       // vBulletin Normal Topic 'topic_sticky = 0'
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * Verify the topic reply count.
+	 *
+	 * @param int $count vBulletin v3.x reply count
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_reply_count( $count = 1 ) {
+		$count = absint( (int) $count - 1 );
+		return $count;
+	}
+
+	/**
+	 * Translate the post status from vBulletin v3.x numerics to WordPress's strings.
+	 *
+	 * @param int $status vBulletin v3.x numeric topic status
+	 * @return string WordPress safe
+	 */
+	public function callback_topic_status( $status = 1 ) {
+		switch ( $status ) {
+			case 0 :
+				$status = 'closed';
+				break;
+
+			case 1  :
+			default :
+				$status = 'publish';
+				break;
+		}
+		return $status;
+	}
+
+	/**
+	 * This callback processes any custom parser.php attributes and custom code with preg_replace
+	 */
+	protected function callback_html( $field ) {
+
+		// Strips vBulletin custom HTML first from $field before parsing $field to parser.php
+		$vbulletin_markup = $field;
+		$vbulletin_markup = html_entity_decode( $vbulletin_markup );
+
+		// Replace '[QUOTE]' with '<blockquote>'
+		$vbulletin_markup = preg_replace( '/\[QUOTE\]/', '<blockquote>', $vbulletin_markup );
+		// Replace '[QUOTE=User Name($1);PostID($2)]' with '<em>@$1 $2 wrote:</em><blockquote>"
+		$vbulletin_markup = preg_replace( '/\[QUOTE=(.*?);(.*?)\]/' , '<em>@$1 $2 wrote:</em><blockquote>', $vbulletin_markup );
+		// Replace '[/QUOTE]' with '</blockquote>'
+		$vbulletin_markup = preg_replace( '/\[\/QUOTE\]/', '</blockquote>', $vbulletin_markup );
+		// Replace '[MENTION=###($1)]User Name($2)[/MENTION]' with '@$2"
+		$vbulletin_markup = preg_replace( '/\[MENTION=(.*?)\](.*?)\[\/MENTION\]/', '@$2', $vbulletin_markup );
+
+		// Replace '[video=youtube;$1]$2[/video]' with '$2"
+		$vbulletin_markup = preg_replace( '/\[video\=youtube;(.*?)\](.*?)\[\/video\]/', '$2', $vbulletin_markup );
+
+		// Now that vBulletin custom HTML has been stripped put the cleaned HTML back in $field
+		$field = $vbulletin_markup;
+
+		// Parse out any bbCodes in $field with the BBCode 'parser.php'
+		return parent::callback_html( $field );
+	}
+}
Index: /tags/2.6.6/src/includes/admin/forums.php
===================================================================
--- /tags/2.6.6/src/includes/admin/forums.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/forums.php	(revision 7162)
@@ -0,0 +1,794 @@
+<?php
+
+/**
+ * bbPress Forum Admin Class
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Forums_Admin' ) ) :
+/**
+ * Loads bbPress forums admin area
+ *
+ * @package bbPress
+ * @subpackage Administration
+ * @since 2.0.0 bbPress (r2464)
+ */
+class BBP_Forums_Admin {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * @var string The post type of this admin component
+	 */
+	private $post_type = '';
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main bbPress forums admin loader
+	 *
+	 * @since 2.0.0 bbPress (r2515)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the admin hooks, actions and filters
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Messages
+		add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
+
+		// Forum Column headers.
+		add_filter( 'manage_' . $this->post_type . '_posts_columns',        array( $this, 'column_headers' )        );
+
+		// Forum Columns (in page row)
+		add_action( 'manage_' . $this->post_type . '_posts_custom_column',  array( $this, 'column_data'    ), 10, 2 );
+		add_filter( 'page_row_actions',                                     array( $this, 'row_actions'    ), 10, 2 );
+
+		// Metabox actions
+		add_action( 'add_meta_boxes', array( $this, 'attributes_metabox'    ) );
+		add_action( 'add_meta_boxes', array( $this, 'moderators_metabox'    ) );
+		add_action( 'add_meta_boxes', array( $this, 'subscriptions_metabox' ) );
+		add_action( 'add_meta_boxes', array( $this, 'comments_metabox'      ) );
+		add_action( 'save_post',      array( $this, 'save_meta_boxes'       ) );
+
+		// Check if there are any bbp_toggle_forum_* requests on admin_init, also have a message displayed
+		add_action( 'load-edit.php', array( $this, 'toggle_forum'        ) );
+		add_action( 'load-edit.php', array( $this, 'toggle_forum_notice' ) );
+
+		// Contextual Help
+		add_action( 'load-edit.php',     array( $this, 'edit_help' ) );
+		add_action( 'load-post.php',     array( $this, 'new_help'  ) );
+		add_action( 'load-post-new.php', array( $this, 'new_help'  ) );
+	}
+
+	/**
+	 * Admin globals
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+		$this->post_type = bbp_get_forum_post_type();
+	}
+
+	/** Contextual Help *******************************************************/
+
+	/**
+	 * Contextual help for bbPress forum edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function edit_help() {
+
+		// Overview
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'overview',
+			'title'		=> __( 'Overview', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'This screen displays the individual forums on your site. You can customize the display of this screen to suit your workflow.', 'bbpress' ) . '</p>'
+		) );
+
+		// Screen Content
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'screen-content',
+			'title'		=> __( 'Screen Content', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can customize the display of this screen&#8217;s contents in a number of ways:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( 'You can hide/display columns based on your needs and decide how many forums to list per screen using the Screen Options tab.',                                                                                                                                'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can filter the list of forums by forum status using the text links in the upper left to show All, Published, or Trashed forums. The default view is to show all forums.',                                                                                 'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can refine the list to show only forums from a specific month by using the dropdown menus above the forums list. Click the Filter button after making your selection. You also can refine the list by clicking on the forum creator in the forums list.', 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Available Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'action-links',
+			'title'		=> __( 'Available Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'Hovering over a row in the forums list will display action links that allow you to manage your forum. You can perform the following actions:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Edit</strong> takes you to the editing screen for that forum. You can also reach that screen by clicking on the forum title.',                                                          'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Close</strong> will mark the selected forum as &#8217;closed&#8217; and disable the ability to post new topics and replies in it.',                                                      'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Trash</strong> removes your forum from this list and places it in the trash, from which you can permanently delete it.',                                                                'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>View</strong> will either show you what your draft forum will look like if you publish it, or take you to your live site to view it. Which link depends on your forum&#8217;s status.', 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Bulk Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'bulk-actions',
+			'title'		=> __( 'Bulk Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can also edit or move multiple forums to the trash at once. Select the forums you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.',           'bbpress' ) . '</p>' .
+				'<p>' . __( 'When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected forums at once. To remove a forum from the grouping, just click the x next to its name in the Bulk Edit area that appears.', 'bbpress' ) . '</p>'
+		) );
+
+		// Help Sidebar
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Contextual help for bbPress forum edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function new_help() {
+
+		$customize_display = '<p>' . __( 'The title field and the big forum editing Area are fixed in place, but you can reposition all the other boxes using drag and drop, and can minimize or expand them by clicking the title bar of each box. Use the Screen Options tab to unhide more boxes (Excerpt, Send Trackbacks, Custom Fields, Discussion, Slug, Author) or to choose a 1- or 2-column layout for this screen.', 'bbpress' ) . '</p>';
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'customize-display',
+			'title'   => __( 'Customizing This Display', 'bbpress' ),
+			'content' => $customize_display,
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'title-forum-editor',
+			'title'   => __( 'Title and Forum Editor', 'bbpress' ),
+			'content' =>
+				'<p>' . __( '<strong>Title</strong> - Enter a title for your forum. After you enter a title, you&#8217;ll see the permalink below, which you can edit.', 'bbpress' ) . '</p>' .
+				'<p>' . __( '<strong>Forum Editor</strong> - Enter the text for your forum. There are two modes of editing: Visual and HTML. Choose the mode by clicking on the appropriate tab. Visual mode gives you a WYSIWYG editor. Click the last icon in the row to get a second row of controls. The HTML mode allows you to enter raw HTML along with your forum text. You can insert media files by clicking the icons above the forum editor and following the directions. You can go to the distraction-free writing screen via the Fullscreen icon in Visual mode (second to last in the top row) or the Fullscreen button in HTML mode (last in the row). Once there, you can make buttons visible by hovering over the top area. Exit Fullscreen back to the regular forum editor.', 'bbpress' ) . '</p>'
+		) );
+
+		$publish_box = '<p>' . __( '<strong>Publish</strong> - You can set the terms of publishing your forum in the Publish box. For Status, Visibility, and Publish (immediately), click on the Edit link to reveal more options. Visibility includes options for password-protecting a forum or making it stay at the top of your blog indefinitely (sticky). Publish (immediately) allows you to set a future or past date and time, so you can schedule a forum to be published in the future or backdate a forum.', 'bbpress' ) . '</p>';
+
+		if ( current_theme_supports( 'forum-thumbnails' ) && post_type_supports( 'forum', 'thumbnail' ) ) {
+			$publish_box .= '<p>' . __( '<strong>Featured Image</strong> - This allows you to associate an image with your forum without inserting it. This is usually useful only if your theme makes use of the featured image as a forum thumbnail on the home page, a custom header, etc.', 'bbpress' ) . '</p>';
+		}
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'forum-attributes',
+			'title'   => __( 'Forum Attributes', 'bbpress' ),
+			'content' =>
+				'<p>' . __( 'Select the attributes that your forum should have:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Type</strong> indicates if the forum is a category or forum. Categories generally contain other forums.',                                                                                'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Status</strong> allows you to close a forum to new topics and forums.',                                                                                                                  'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Visibility</strong> lets you pick the scope of each forum and what users are allowed to access it.',                                                                                     'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Parent</strong> dropdown determines the parent forum. Select the forum or category from the dropdown, or leave the default "No parent" to create the forum at the root of your forums.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Order</strong> allows you to order your forums numerically.',                                                                                                                            'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'publish-box',
+			'title'   => __( 'Publish Box', 'bbpress' ),
+			'content' => $publish_box,
+		) );
+
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Add the forum attributes meta-box
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 */
+	public function attributes_metabox() {
+		add_meta_box(
+			'bbp_forum_attributes',
+			esc_html__( 'Forum Attributes', 'bbpress' ),
+			'bbp_forum_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the forum moderators meta-box
+	 *
+	 * @since 2.6.0 bbPress
+	 */
+	public function moderators_metabox() {
+
+		// Bail if feature not active or user cannot assign moderators
+		if ( ! bbp_allow_forum_mods() || ! current_user_can( 'assign_moderators' ) ) {
+			return;
+		}
+
+		// Moderators
+		add_meta_box(
+			'bbp_moderator_assignment_metabox',
+			esc_html__( 'Forum Moderators', 'bbpress' ),
+			'bbp_moderator_assignment_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the subscriptions meta-box
+	 *
+	 * Allows viewing of users who have subscribed to a forum.
+	 *
+	 * @since 2.6.0 bbPress (r6197)
+	 */
+	public function subscriptions_metabox() {
+
+		// Bail if post_type is not a reply
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Bail if no subscriptions
+		if ( ! bbp_is_subscriptions_active() ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_forum_subscriptions_metabox',
+			esc_html__( 'Subscriptions', 'bbpress' ),
+			'bbp_forum_subscriptions_metabox',
+			$this->post_type,
+			'normal',
+			'high'
+		);
+	}
+
+	/**
+	 * Remove comments & discussion meta-boxes if comments are not supported
+	 *
+	 * @since 2.6.0 bbPress (r6186)
+	 */
+	public function comments_metabox() {
+		if ( ! post_type_supports( $this->post_type, 'comments' ) ) {
+			remove_meta_box( 'commentstatusdiv', $this->post_type, 'normal' );
+			remove_meta_box( 'commentsdiv',      $this->post_type, 'normal' );
+		}
+	}
+
+	/**
+	 * Pass the forum attributes for processing
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @param int $forum_id Forum id
+	 * @return int Forum id
+	 */
+	public function save_meta_boxes( $forum_id ) {
+
+		// Bail if doing an autosave
+		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
+			return $forum_id;
+		}
+
+		// Bail if not a post request
+		if ( ! bbp_is_post_request() ) {
+			return $forum_id;
+		}
+
+		// Nonce check
+		if ( empty( $_POST['bbp_forum_metabox'] ) || ! wp_verify_nonce( $_POST['bbp_forum_metabox'], 'bbp_forum_metabox_save' ) ) {
+			return $forum_id;
+		}
+
+		// Only save for forum post-types
+		if ( ! bbp_is_forum( $forum_id ) ) {
+			return $forum_id;
+		}
+
+		// Bail if current user cannot edit this forum
+		if ( ! current_user_can( 'edit_forum', $forum_id ) ) {
+			return $forum_id;
+		}
+
+		// Parent ID
+		$parent_id = ( ! empty( $_POST['parent_id'] ) && is_numeric( $_POST['parent_id'] ) )
+			? (int) $_POST['parent_id']
+			: 0;
+
+		// Update the forum meta bidness
+		bbp_update_forum( array(
+			'forum_id'    => $forum_id,
+			'post_parent' => $parent_id
+		) );
+
+		do_action( 'bbp_forum_attributes_metabox_save', $forum_id );
+
+		return $forum_id;
+	}
+
+	/**
+	 * Toggle forum
+	 *
+	 * Handles the admin-side opening/closing of forums
+	 *
+	 * @since 2.6.0 bbPress (r5254)
+	 */
+	public function toggle_forum() {
+
+		// Bail if not a forum toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['action'] ) || empty( $_GET['forum_id'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed action
+		$action = sanitize_key( $_GET['action'] );
+		if ( empty( $action ) || ! in_array( $action, $this->get_allowed_action_toggles(), true ) ) {
+			return;
+		}
+
+		// Bail if forum is missing
+		$forum_id = bbp_get_forum_id( $_GET['forum_id'] );
+		if ( ! bbp_get_forum( $forum_id ) ) {
+			wp_die( esc_html__( 'The forum was not found.', 'bbpress' ) );
+		}
+
+		// What is the user doing here?
+		if ( ! current_user_can( 'edit_forum', $forum_id ) ) {
+			wp_die( esc_html__( 'You do not have permission to do that.', 'bbpress' ) );
+		}
+
+		// Defaults
+		$post_data = array( 'ID' => $forum_id );
+		$message   = '';
+		$success   = false;
+
+		switch ( $action ) {
+			case 'bbp_toggle_forum_close' :
+				check_admin_referer( 'close-forum_' . $forum_id );
+
+				$is_open = bbp_is_forum_open( $forum_id );
+				$message = ( true === $is_open )
+					? 'closed'
+					: 'opened';
+				$success = ( true === $is_open )
+					? bbp_close_forum( $forum_id )
+					: bbp_open_forum( $forum_id );
+
+				break;
+		}
+
+		// Setup the message
+		$retval = array(
+			'bbp_forum_toggle_notice' => $message,
+			'forum_id'                => $forum_id
+		);
+
+		// Prepare for failure
+		if ( ( false === $success ) || is_wp_error( $success ) ) {
+			$retval['failed'] = '1';
+		}
+
+		// Filter all message args
+		$retval = apply_filters( 'bbp_toggle_forum_action_admin', $retval, $forum_id, $action );
+
+		// Do additional forum toggle actions (admin side)
+		do_action( 'bbp_toggle_forum_admin', $success, $post_data, $action, $retval );
+
+		// Redirect back to the forum
+		$redirect = add_query_arg( $retval, remove_query_arg( array( 'action', 'forum_id' ) ) );
+		bbp_redirect( $redirect );
+	}
+
+	/**
+	 * Toggle forum notices
+	 *
+	 * Display the success/error notices from
+	 * {@link BBP_Admin::toggle_forum()}
+	 *
+	 * @since 2.6.0 bbPress (r5254)
+	 */
+	public function toggle_forum_notice() {
+
+		// Bail if missing forum toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['forum_id'] ) || empty( $_GET['bbp_forum_toggle_notice'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed notice
+		$notice = sanitize_key( $_GET['bbp_forum_toggle_notice'] );
+		if ( empty( $notice ) || ! in_array( $notice, $this->get_allowed_notice_toggles(), true ) ) {
+			return;
+		}
+
+		// Bail if no forum_id or notice
+		$forum_id = bbp_get_forum_id( $_GET['forum_id'] );
+		if ( empty( $forum_id ) ) {
+			return;
+		}
+
+		// Bail if forum is missing
+		if ( ! bbp_get_forum( $forum_id ) ) {
+			return;
+		}
+
+		// Use the title in the responses
+		$forum_title = bbp_get_forum_title( $forum_id );
+		$is_failure  = ! empty( $_GET['failed'] );
+		$message     = '';
+
+		switch ( $notice ) {
+			case 'opened' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem opening the forum "%1$s".', 'bbpress' ), $forum_title )
+					: sprintf( esc_html__( 'Forum "%1$s" successfully opened.',             'bbpress' ), $forum_title );
+				break;
+
+			case 'closed' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem closing the forum "%1$s".', 'bbpress' ), $forum_title )
+					: sprintf( esc_html__( 'Forum "%1$s" successfully closed.',             'bbpress' ), $forum_title );
+				break;
+		}
+
+		// Do additional forum toggle notice filters (admin side)
+		$message = apply_filters( 'bbp_toggle_forum_notice_admin', $message, $forum_id, $notice, $is_failure );
+		$class   = ( $is_failure === true )
+			? 'error'
+			: 'updated';
+
+		// Add the notice
+		bbp_admin()->add_notice( $message, $class, true );
+	}
+
+	/**
+	 * Returns an array of keys used to sort row actions
+	 *
+	 * @since 2.6.0 bbPress (r6771)
+	 *
+	 * @return array
+	 */
+	private function get_row_action_sort_order() {
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_forum_row_action_sort_order', array(
+			'edit',
+			'closed',
+			'trash',
+			'untrash',
+			'delete',
+			'view'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_notice_toggles() {
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_forums_allowed_notice_toggles', array(
+			'opened',
+			'closed'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_action_toggles() {
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_forums_allowed_action_toggles', array(
+			'bbp_toggle_forum_close'
+		) );
+	}
+
+	/**
+	 * Manage the column headers for the forums page
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param array $columns The columns
+	 *
+	 * @return array $columns bbPress forum columns
+	 */
+	public function column_headers( $columns ) {
+
+		// Set list table column headers
+		$columns = array(
+			'cb'                    => '<input type="checkbox" />',
+			'title'                 => esc_html__( 'Forum',      'bbpress' ),
+			'bbp_forum_topic_count' => esc_html__( 'Topics',     'bbpress' ),
+			'bbp_forum_reply_count' => esc_html__( 'Replies',    'bbpress' ),
+			'bbp_forum_mods'        => esc_html__( 'Moderators', 'bbpress' ),
+			'author'                => esc_html__( 'Creator',    'bbpress' ),
+			'bbp_forum_created'     => esc_html__( 'Created' ,   'bbpress' ),
+			'bbp_forum_freshness'   => esc_html__( 'Last Post',  'bbpress' )
+		);
+
+		// Remove forum mods column if not enabled
+		if ( ! bbp_allow_forum_mods() ) {
+			unset( $columns['bbp_forum_mods'] );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_forums_column_headers', $columns );
+	}
+
+	/**
+	 * Print extra columns for the forums page
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param string $column Column
+	 * @param int $forum_id Forum id
+	 */
+	public function column_data( $column, $forum_id ) {
+
+		switch ( $column ) {
+			case 'bbp_forum_topic_count' :
+				bbp_forum_topic_count( $forum_id );
+				break;
+
+			case 'bbp_forum_reply_count' :
+				bbp_forum_reply_count( $forum_id );
+				break;
+
+			case 'bbp_forum_mods' :
+				bbp_moderator_list( $forum_id, array(
+					'before' => '',
+					'after'  => '',
+					'none'   => esc_html__( '&mdash;', 'bbpress' )
+				) );
+				break;
+
+			case 'bbp_forum_created':
+				printf( '%1$s <br /> %2$s',
+					get_the_date(),
+					esc_attr( get_the_time() )
+				);
+
+				break;
+
+			case 'bbp_forum_freshness' :
+				$last_active = bbp_get_forum_last_active_time( $forum_id, false );
+				if ( ! empty( $last_active ) ) {
+					echo esc_html( $last_active );
+				} else {
+					esc_html_e( 'No Topics', 'bbpress' );
+				}
+
+				break;
+
+			default:
+				do_action( 'bbp_admin_forums_column_data', $column, $forum_id );
+				break;
+		}
+	}
+
+	/**
+	 * Forum Row actions
+	 *
+	 * Remove the quick-edit action link and display the description under
+	 * the forum title and add the open/close links
+	 *
+	 * @since 2.0.0 bbPress (r2577)
+	 *
+	 * @param array  $actions Actions
+	 * @param object $forum   Forum object
+	 *
+	 * @return array $actions Actions
+	 */
+	public function row_actions( $actions = array(), $forum = false ) {
+
+		// Disable quick edit (too much to do here)
+		unset( $actions['inline hide-if-no-js'] );
+
+		// Only show the actions if the user is capable of viewing them :)
+		if ( current_user_can( 'edit_forum', $forum->ID ) ) {
+
+			// Show the 'close' and 'open' link on published, private, hidden and closed posts only
+			if ( in_array( $forum->post_status, array( bbp_get_public_status_id(), bbp_get_private_status_id(), bbp_get_hidden_status_id(), bbp_get_closed_status_id() ), true ) ) {
+				$close_uri = wp_nonce_url( add_query_arg( array( 'forum_id' => $forum->ID, 'action' => 'bbp_toggle_forum_close' ), remove_query_arg( array( 'bbp_forum_toggle_notice', 'forum_id', 'failed', 'super' ) ) ), 'close-forum_' . $forum->ID );
+				if ( bbp_is_forum_open( $forum->ID ) ) {
+					$actions['closed'] = '<a href="' . esc_url( $close_uri ) . '" title="' . esc_attr__( 'Close this forum', 'bbpress' ) . '">' . _x( 'Close', 'Close a Forum', 'bbpress' ) . '</a>';
+				} else {
+					$actions['closed'] = '<a href="' . esc_url( $close_uri ) . '" title="' . esc_attr__( 'Open this forum',  'bbpress' ) . '">' . _x( 'Open',  'Open a Forum',  'bbpress' ) . '</a>';
+				}
+			}
+		}
+
+		// Only show content if user can read it and there is no password
+		if ( current_user_can( 'read_forum', $forum->ID ) && ! post_password_required( $forum ) ) {
+
+			// Get the forum description
+			$content = bbp_get_forum_content( $forum->ID );
+
+			// Only proceed if there is a description
+			if ( ! empty( $content ) ) {
+				echo '<div class="bbp-escaped-content">' . esc_html( wp_trim_excerpt( $content, $forum ) ) . '</div>';
+			}
+		}
+
+		// Sort & return
+		return $this->sort_row_actions( $actions );
+	}
+
+	/**
+	 * Sort row actions by key
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param array $actions
+	 *
+	 * @return array
+	 */
+	private function sort_row_actions( $actions = array() ) {
+
+		// Return value
+		$retval = array();
+
+		// Known row actions, in sort order
+		$known_actions = $this->get_row_action_sort_order();
+
+		// Sort known actions, and keep any unknown ones
+		foreach ( $known_actions as $key ) {
+			if ( isset( $actions[ $key ] ) ) {
+				$retval[ $key ] = $actions[ $key ];
+				unset( $actions[ $key ] );
+			}
+		}
+
+		// Combine & return
+		return $retval + $actions;
+	}
+
+	/**
+	 * Custom user feedback messages for forum post type
+	 *
+	 * @since 2.0.0 bbPress (r3080)
+	 *
+	 * @global int $post_ID
+	 *
+	 * @param array $messages
+	 *
+	 * @return array
+	 */
+	public function updated_messages( $messages ) {
+		global $post_ID;
+
+		// URL for the current forum
+		$forum_url = bbp_get_forum_permalink( $post_ID );
+
+		// Current forum's post_date
+		$post_date = bbp_get_global_post_field( 'post_date', 'raw' );
+
+		// Messages array
+		$messages[ $this->post_type ] = array(
+			0 =>  '', // Left empty on purpose
+
+			// Updated
+			1 =>  sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Forum updated.', 'bbpress' ),
+				$forum_url,
+				esc_html__( 'View forum', 'bbpress' )
+			),
+
+			// Custom field updated
+			2 => esc_html__( 'Custom field updated.', 'bbpress' ),
+
+			// Custom field deleted
+			3 => esc_html__( 'Custom field deleted.', 'bbpress' ),
+
+			// Forum updated
+			4 => esc_html__( 'Forum updated.', 'bbpress' ),
+
+			// Restored from revision
+			// translators: %s: date and time of the revision
+			5 => isset( $_GET['revision'] )
+				? sprintf( esc_html__( 'Forum restored to revision from %s', 'bbpress' ), wp_post_revision_title( (int) $_GET['revision'], false ) )
+				: false,
+
+			// Forum created
+			6 => sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Forum created.', 'bbpress' ),
+				$forum_url,
+				esc_html__( 'View forum', 'bbpress' )
+			),
+
+			// Forum saved
+			7 => esc_html__( 'Forum saved.', 'bbpress' ),
+
+			// Forum submitted
+			8 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Forum submitted.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $forum_url ) ),
+				esc_html__( 'Preview forum', 'bbpress' )
+			),
+
+			// Forum scheduled
+			9 => sprintf(
+				'%1$s <a target="_blank" href="%2$s">%3$s</a>',
+				sprintf(
+					esc_html__( 'Forum scheduled for: %s.', 'bbpress' ),
+					// translators: Publish box date format, see http://php.net/date
+					'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'bbpress' ), strtotime( $post_date ) ) . '</strong>'
+				),
+				$forum_url,
+				__( 'Preview forum', 'bbpress' )
+			),
+
+			// Forum draft updated
+			10 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Forum draft updated.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $forum_url ) ),
+				esc_html__( 'Preview forum', 'bbpress' )
+			),
+		);
+
+		return $messages;
+	}
+}
+endif; // class_exists check
+
+/**
+ * Setup bbPress Forums Admin
+ *
+ * This is currently here to make hooking and unhooking of the admin UI easy.
+ * It could use dependency injection in the future, but for now this is easier.
+ *
+ * @since 2.0.0 bbPress (r2596)
+ *
+ * @param WP_Screen $current_screen Current screen object
+ */
+function bbp_admin_forums( $current_screen ) {
+
+	// Bail if not a forum screen
+	if ( empty( $current_screen->post_type ) || ( bbp_get_forum_post_type() !== $current_screen->post_type ) ) {
+		return;
+	}
+
+	// Init the forums admin
+	bbp_admin()->forums = new BBP_Forums_Admin();
+}
Index: /tags/2.6.6/src/includes/admin/index.php
===================================================================
--- /tags/2.6.6/src/includes/admin/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/admin/metaboxes.php
===================================================================
--- /tags/2.6.6/src/includes/admin/metaboxes.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/metaboxes.php	(revision 7162)
@@ -0,0 +1,832 @@
+<?php
+
+/**
+ * bbPress Admin Metaboxes
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+/** Dashboard *****************************************************************/
+
+/**
+ * Filter the Dashboard "at a glance" items and append bbPress elements to it.
+ *
+ * @since 2.6.0 bbPress (r5268)
+ *
+ * @param array $elements
+ * @return array
+ */
+function bbp_filter_dashboard_glance_items( $elements = array() ) {
+
+	// Bail if user cannot spectate
+	if ( ! current_user_can( 'spectate' ) ) {
+		return $elements;
+	}
+
+	// Get the statistics
+	$r = bbp_get_statistics( array(
+		'count_pending_topics'  => false,
+		'count_private_topics'  => false,
+		'count_spammed_topics'  => false,
+		'count_trashed_topics'  => false,
+		'count_pending_replies' => false,
+		'count_private_replies' => false,
+		'count_spammed_replies' => false,
+		'count_trashed_replies' => false,
+		'count_empty_tags'      => false
+	) );
+
+	// Users
+	if ( isset( $r['user_count'] ) ) {
+		$link       = admin_url( 'users.php' );
+		$text       = sprintf( _n( '%s User', '%s Users', $r['user_count_int'], 'bbpress' ), $r['user_count'] );
+		$elements[] = current_user_can( 'edit_users' )
+			? '<a href="' . esc_url( $link ) . '" class="bbp-glance-users">' . esc_html( $text ) . '</a>'
+			: esc_html( $text );
+	}
+
+	// Forums
+	if ( isset( $r['forum_count'] ) ) {
+		$link       = add_query_arg( array( 'post_type' => bbp_get_forum_post_type() ), admin_url( 'edit.php' ) );
+		$text       = sprintf( _n( '%s Forum', '%s Forums', $r['forum_count_int'], 'bbpress' ), $r['forum_count'] );
+		$elements[] = current_user_can( 'publish_forums' )
+			? '<a href="' . esc_url( $link ) . '" class="bbp-glance-forums">' . esc_html( $text ) . '</a>'
+			: esc_html( $text );
+	}
+
+	// Topics
+	if ( isset( $r['topic_count'] ) ) {
+		$link       = add_query_arg( array( 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit.php' ) );
+		$text       = sprintf( _n( '%s Topic', '%s Topics', $r['topic_count_int'], 'bbpress' ), $r['topic_count'] );
+		$elements[] = current_user_can( 'publish_topics' )
+			? '<a href="' . esc_url( $link ) . '" class="bbp-glance-topics">' . esc_html( $text ) . '</a>'
+			: esc_html( $text );
+	}
+
+	// Replies
+	if ( isset( $r['reply_count'] ) ) {
+		$link       = add_query_arg( array( 'post_type' => bbp_get_reply_post_type() ), admin_url( 'edit.php' ) );
+		$text       = sprintf( _n( '%s Reply', '%s Replies', $r['reply_count_int'], 'bbpress' ), $r['reply_count'] );
+		$elements[] = current_user_can( 'publish_replies' )
+			? '<a href="' . esc_url( $link ) . '" class="bbp-glance-replies">' . esc_html( $text ) . '</a>'
+			: esc_html( $text );
+	}
+
+	// Topic Tags
+	if ( bbp_allow_topic_tags() && isset( $r['topic_tag_count'] ) ) {
+		$link       = add_query_arg( array( 'taxonomy' => bbp_get_topic_tag_tax_id(), 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit-tags.php' ) );
+		$text       = sprintf( _n( '%s Topic Tag', '%s Topic Tags', $r['topic_tag_count_int'], 'bbpress' ), $r['topic_tag_count'] );
+		$elements[] = current_user_can( 'manage_topic_tags' )
+			? '<a href="' . esc_url( $link ) . '" class="bbp-glance-topic-tags">' . esc_html( $text ) . '</a>'
+			: esc_html( $text );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_dashboard_at_a_glance', $elements, $r );
+}
+
+/**
+ * bbPress Dashboard Right Now Widget
+ *
+ * Adds a dashboard widget with forum statistics
+ *
+ * @since 2.0.0 bbPress (r2770)
+ *
+ * @deprecated 2.6.0 bbPress (r5268)
+ */
+function bbp_dashboard_widget_right_now() {
+
+	// Get the statistics
+	$r = bbp_get_statistics(); ?>
+
+	<div class="table table_content">
+
+		<p class="sub"><?php esc_html_e( 'Discussion', 'bbpress' ); ?></p>
+
+		<table>
+
+			<tr class="first">
+
+				<?php
+					$num  = $r['forum_count'];
+					$text = _n( 'Forum', 'Forums', $r['forum_count_int'], 'bbpress' );
+					if ( current_user_can( 'publish_forums' ) ) {
+						$link = add_query_arg( array( 'post_type' => bbp_get_forum_post_type() ), admin_url( 'edit.php' ) );
+						$num  = '<a href="' . esc_url( $link ) . '">' . $num  . '</a>';
+						$text = '<a href="' . esc_url( $link ) . '">' . $text . '</a>';
+					}
+				?>
+
+				<td class="first b b-forums"><?php echo $num; ?></td>
+				<td class="t forums"><?php echo $text; ?></td>
+
+			</tr>
+
+			<tr>
+
+				<?php
+					$num  = $r['topic_count'];
+					$text = _n( 'Topic', 'Topics', $r['topic_count_int'], 'bbpress' );
+					if ( current_user_can( 'publish_topics' ) ) {
+						$link = add_query_arg( array( 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit.php' ) );
+						$num  = '<a href="' . esc_url( $link ) . '">' . $num  . '</a>';
+						$text = '<a href="' . esc_url( $link ) . '">' . $text . '</a>';
+					}
+				?>
+
+				<td class="first b b-topics"><?php echo $num; ?></td>
+				<td class="t topics"><?php echo $text; ?></td>
+
+			</tr>
+
+			<tr>
+
+				<?php
+					$num  = $r['reply_count'];
+					$text = _n( 'Reply', 'Replies', $r['reply_count_int'], 'bbpress' );
+					if ( current_user_can( 'publish_replies' ) ) {
+						$link = add_query_arg( array( 'post_type' => bbp_get_reply_post_type() ), admin_url( 'edit.php' ) );
+						$num  = '<a href="' . esc_url( $link ) . '">' . $num  . '</a>';
+						$text = '<a href="' . esc_url( $link ) . '">' . $text . '</a>';
+					}
+				?>
+
+				<td class="first b b-replies"><?php echo $num; ?></td>
+				<td class="t replies"><?php echo $text; ?></td>
+
+			</tr>
+
+			<?php if ( bbp_allow_topic_tags() ) : ?>
+
+				<tr>
+
+					<?php
+						$num  = $r['topic_tag_count'];
+						$text = _n( 'Topic Tag', 'Topic Tags', $r['topic_tag_count_int'], 'bbpress' );
+						if ( current_user_can( 'manage_topic_tags' ) ) {
+							$link = add_query_arg( array( 'taxonomy' => bbp_get_topic_tag_tax_id(), 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit-tags.php' ) );
+							$num  = '<a href="' . esc_url( $link ) . '">' . $num  . '</a>';
+							$text = '<a href="' . esc_url( $link ) . '">' . $text . '</a>';
+						}
+					?>
+
+					<td class="first b b-topic_tags"><span class="total-count"><?php echo $num; ?></span></td>
+					<td class="t topic_tags"><?php echo $text; ?></td>
+
+				</tr>
+
+			<?php endif; ?>
+
+			<?php do_action( 'bbp_dashboard_widget_right_now_content_table_end' ); ?>
+
+		</table>
+
+	</div>
+
+
+	<div class="table table_discussion">
+
+		<p class="sub"><?php esc_html_e( 'Users &amp; Moderation', 'bbpress' ); ?></p>
+
+		<table>
+
+			<tr class="first">
+
+				<?php
+					$num  = $r['user_count'];
+					$text = _n( 'User', 'Users', $r['user_count_int'], 'bbpress' );
+					if ( current_user_can( 'edit_users' ) ) {
+						$link = admin_url( 'users.php' );
+						$num  = '<a href="' . $link . '">' . $num  . '</a>';
+						$text = '<a href="' . $link . '">' . $text . '</a>';
+					}
+				?>
+
+				<td class="b b-users"><span class="total-count"><?php echo $num; ?></span></td>
+				<td class="last t users"><?php echo $text; ?></td>
+
+			</tr>
+
+			<?php if ( isset( $r['topic_count_hidden'] ) ) : ?>
+
+				<tr>
+
+					<?php
+						$num  = $r['topic_count_hidden'];
+						$text = _n( 'Hidden Topic', 'Hidden Topics', $r['topic_count_hidden_int'], 'bbpress' );
+						$link = add_query_arg( array( 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit.php' ) );
+						if ( '0' !== $num ) {
+							$link = add_query_arg( array( 'post_status' => bbp_get_spam_status_id() ), $link );
+						}
+						$num  = '<a href="' . esc_url( $link ) . '" title="' . esc_attr( $r['hidden_topic_title'] ) . '">' . $num  . '</a>';
+						$text = '<a class="waiting" href="' . esc_url( $link ) . '" title="' . esc_attr( $r['hidden_topic_title'] ) . '">' . $text . '</a>';
+					?>
+
+					<td class="b b-hidden-topics"><?php echo $num; ?></td>
+					<td class="last t hidden-replies"><?php echo $text; ?></td>
+
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( isset( $r['reply_count_hidden'] ) ) : ?>
+
+				<tr>
+
+					<?php
+						$num  = $r['reply_count_hidden'];
+						$text = _n( 'Hidden Reply', 'Hidden Replies', $r['reply_count_hidden_int'], 'bbpress' );
+						$link = add_query_arg( array( 'post_type' => bbp_get_reply_post_type() ), admin_url( 'edit.php' ) );
+						if ( '0' !== $num ) {
+							$link = add_query_arg( array( 'post_status' => bbp_get_spam_status_id() ), $link );
+						}
+						$num  = '<a href="' . esc_url( $link ) . '" title="' . esc_attr( $r['hidden_reply_title'] ) . '">' . $num  . '</a>';
+						$text = '<a class="waiting" href="' . esc_url( $link ) . '" title="' . esc_attr( $r['hidden_reply_title'] ) . '">' . $text . '</a>';
+					?>
+
+					<td class="b b-hidden-replies"><?php echo $num; ?></td>
+					<td class="last t hidden-replies"><?php echo $text; ?></td>
+
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( bbp_allow_topic_tags() && isset( $r['empty_topic_tag_count'] ) ) : ?>
+
+				<tr>
+
+					<?php
+						$num  = $r['empty_topic_tag_count'];
+						$text = _n( 'Empty Topic Tag', 'Empty Topic Tags', $r['empty_topic_tag_count_int'], 'bbpress' );
+						$link = add_query_arg( array( 'taxonomy' => bbp_get_topic_tag_tax_id(), 'post_type' => bbp_get_topic_post_type() ), admin_url( 'edit-tags.php' ) );
+						$num  = '<a href="' . esc_url( $link ) . '">' . $num  . '</a>';
+						$text = '<a class="waiting" href="' . esc_url( $link ) . '">' . $text . '</a>';
+					?>
+
+					<td class="b b-hidden-topic-tags"><?php echo $num; ?></td>
+					<td class="last t hidden-topic-tags"><?php echo $text; ?></td>
+
+				</tr>
+
+			<?php endif; ?>
+
+			<?php do_action( 'bbp_dashboard_widget_right_now_discussion_table_end' ); ?>
+
+		</table>
+
+	</div>
+
+	<?php do_action( 'bbp_dashboard_widget_right_now_table_end' ); ?>
+
+	<div class="versions">
+
+		<span id="wp-version-message">
+			<?php printf( __( 'You are using <span class="b">bbPress %s</span>.', 'bbpress' ), bbp_get_version() ); ?>
+		</span>
+
+	</div>
+
+	<br class="clear" />
+
+	<?php
+
+	do_action( 'bbp_dashboard_widget_right_now_end' );
+}
+
+/** Forums ********************************************************************/
+
+/**
+ * Forum meta-box
+ *
+ * The meta-box that holds all of the additional forum information
+ *
+ * @since 2.0.0 bbPress (r2744)
+ */
+function bbp_forum_metabox( $post ) {
+
+	// Post ID
+	$post_parent = bbp_get_global_post_field( 'post_parent', 'raw'  );
+	$menu_order  = bbp_get_global_post_field( 'menu_order',  'edit' );
+
+	/** Type ******************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Type:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="bbp_forum_type_select"><?php esc_html_e( 'Type:', 'bbpress' ) ?></label>
+		<?php bbp_form_forum_type_dropdown( array( 'forum_id' => $post->ID ) ); ?>
+	</p>
+
+	<?php
+
+	/** Status ****************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Status:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="bbp_forum_status_select"><?php esc_html_e( 'Status:', 'bbpress' ) ?></label>
+		<?php bbp_form_forum_status_dropdown( array( 'forum_id' => $post->ID ) ); ?>
+	</p>
+
+	<?php
+
+	/** Visibility ************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Visibility:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="bbp_forum_visibility_select"><?php esc_html_e( 'Visibility:', 'bbpress' ) ?></label>
+		<?php bbp_form_forum_visibility_dropdown( array( 'forum_id' => $post->ID ) ); ?>
+	</p>
+
+	<hr />
+
+	<?php
+
+	/** Parent ****************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Parent:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="parent_id"><?php esc_html_e( 'Forum Parent', 'bbpress' ); ?></label>
+		<?php bbp_dropdown( array(
+			'post_type'          => bbp_get_forum_post_type(),
+			'selected'           => $post_parent,
+			'numberposts'        => -1,
+			'orderby'            => 'title',
+			'order'              => 'ASC',
+			'walker'             => '',
+			'exclude'            => $post->ID,
+
+			// Output-related
+			'select_id'          => 'parent_id',
+			'options_only'       => false,
+			'show_none'          => esc_html__( '&mdash; No parent &mdash;', 'bbpress' ),
+			'disable_categories' => false,
+			'disabled'           => ''
+		) ); ?>
+	</p>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Order:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="menu_order"><?php esc_html_e( 'Forum Order', 'bbpress' ); ?></label>
+		<input name="menu_order" type="number" step="1" size="4" id="menu_order" value="<?php echo esc_attr( $menu_order ); ?>" />
+	</p>
+
+	<input name="ping_status" type="hidden" id="ping_status" value="open" />
+
+	<?php
+	wp_nonce_field( 'bbp_forum_metabox_save', 'bbp_forum_metabox' );
+	do_action( 'bbp_forum_metabox', $post );
+}
+
+/** Topics ********************************************************************/
+
+/**
+ * Topic meta-box
+ *
+ * The meta-box that holds all of the additional topic information
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_topic_metabox( $post ) {
+
+	/** Type ******************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Type:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="bbp_stick_topic"><?php esc_html_e( 'Topic Type', 'bbpress' ); ?></label>
+		<?php bbp_form_topic_type_dropdown( array( 'topic_id' => $post->ID ) ); ?>
+	</p>
+
+	<?php
+
+	/** Status ****************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Status:', 'bbpress' ); ?></strong>
+		<input type="hidden" name="hidden_post_status" id="hidden_post_status" value="<?php echo esc_attr( ( 'auto-draft' === $post->post_status ) ? 'draft' : $post->post_status ); ?>" />
+		<label class="screen-reader-text" for="bbp_open_close_topic"><?php esc_html_e( 'Select whether to open or close the topic.', 'bbpress' ); ?></label>
+		<?php bbp_form_topic_status_dropdown( array( 'select_id' => 'post_status', 'topic_id' => $post->ID ) ); ?>
+	</p>
+
+	<?php
+
+	/** Parent *****************************************************************/
+
+	?>
+
+	<hr />
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Forum:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="parent_id"><?php esc_html_e( 'Forum', 'bbpress' ); ?></label>
+		<?php bbp_dropdown( array(
+			'post_type'          => bbp_get_forum_post_type(),
+			'selected'           => bbp_get_topic_forum_id( $post->ID ),
+			'numberposts'        => -1,
+			'orderby'            => 'title',
+			'order'              => 'ASC',
+			'walker'             => '',
+			'exclude'            => '',
+
+			// Output-related
+			'select_id'          => 'parent_id',
+			'options_only'       => false,
+			'show_none'          => esc_html__( '&mdash; No forum &mdash;', 'bbpress' ),
+			'disable_categories' => current_user_can( 'edit_forums' ),
+			'disabled'           => ''
+		) ); ?>
+	</p>
+
+	<input name="ping_status" type="hidden" id="ping_status" value="open" />
+
+	<?php
+	wp_nonce_field( 'bbp_topic_metabox_save', 'bbp_topic_metabox' );
+	do_action( 'bbp_topic_metabox', $post );
+}
+
+/** Replies *******************************************************************/
+
+/**
+ * Reply meta-box
+ *
+ * The meta-box that holds all of the additional reply information
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_reply_metabox( $post ) {
+
+	// Get some meta
+	$reply_topic_id = bbp_get_reply_topic_id( $post->ID );
+	$reply_forum_id = bbp_get_reply_forum_id( $post->ID );
+	$topic_forum_id = bbp_get_topic_forum_id( $reply_topic_id );
+
+	/** Status ****************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Status:', 'bbpress' ); ?></strong>
+		<input type="hidden" name="hidden_post_status" id="hidden_post_status" value="<?php echo esc_attr( ( 'auto-draft' === $post->post_status ) ? 'draft' : $post->post_status ); ?>" />
+		<label class="screen-reader-text" for="post_status"><?php esc_html_e( 'Select what status to give the reply.', 'bbpress' ); ?></label>
+		<?php bbp_form_reply_status_dropdown( array( 'select_id' => 'post_status', 'reply_id' => $post->ID ) ); ?>
+	</p>
+
+	<hr />
+
+	<?php
+
+	/** Forum *****************************************************************/
+
+	// Only allow individual manipulation of reply forum if there is a mismatch
+	if ( ( $reply_forum_id !== $topic_forum_id ) && ( current_user_can( 'edit_others_replies' ) || current_user_can( 'moderate', $post->ID ) ) ) : ?>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'Forum:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_forum_id"><?php esc_html_e( 'Forum', 'bbpress' ); ?></label>
+			<?php bbp_dropdown( array(
+				'post_type'          => bbp_get_forum_post_type(),
+				'selected'           => $reply_forum_id,
+				'numberposts'        => -1,
+				'orderby'            => 'title',
+				'order'              => 'ASC',
+				'walker'             => '',
+				'exclude'            => '',
+
+				// Output-related
+				'select_id'          => 'bbp_forum_id',
+				'options_only'       => false,
+				'show_none'          => esc_html__( '&mdash; No reply &mdash;', 'bbpress' ),
+				'disable_categories' => current_user_can( 'edit_forums' ),
+				'disabled'           => ''
+			) ); ?>
+		</p>
+
+	<?php endif;
+
+	/** Topic *****************************************************************/
+
+	?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'Topic:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="parent_id"><?php esc_html_e( 'Topic', 'bbpress' ); ?></label>
+		<input name="parent_id" id="bbp_topic_id" type="text" value="<?php echo esc_attr( $reply_topic_id ); ?>" data-ajax-url="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_suggest_topic' ), admin_url( 'admin-ajax.php', 'relative' ) ), 'bbp_suggest_topic_nonce' ) ); ?>" />
+	</p>
+
+	<?php
+
+	/** Reply To **************************************************************/
+
+	// Only show reply-to drop-down when editing an existing reply
+	if ( ! empty( $reply_topic_id ) ) : ?>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'Reply To:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_reply_to"><?php esc_html_e( 'Reply To', 'bbpress' ); ?></label>
+			<?php bbp_reply_to_dropdown( $post->ID ); ?>
+		</p>
+
+	<?php
+
+	endif;
+
+	?>
+
+	<input name="ping_status" type="hidden" id="ping_status" value="open" />
+
+	<?php
+	wp_nonce_field( 'bbp_reply_metabox_save', 'bbp_reply_metabox' );
+	do_action( 'bbp_reply_metabox', $post );
+}
+
+/**
+ * Output the topic replies meta-box
+ *
+ * @since 2.6.0 bbPress (r5886)
+ *
+ * @param object $topic
+ *
+ * @return void
+ */
+function bbp_topic_replies_metabox( $topic = false ) {
+
+	// Bail if no topic to load replies for
+	if ( empty( $topic ) ) {
+		return;
+	}
+
+	// Pull in the list table class
+	if ( ! class_exists( 'BBP_Topic_Replies_List_Table' ) ) {
+		require_once bbp_admin()->admin_dir . '/classes/class-bbp-topic-replies-list-table.php';
+	}
+
+	// Look for pagination value
+	$page = isset( $_REQUEST['page'] )
+		? (int) $_REQUEST['page']
+		: 0;
+
+	// Load up the list table
+	$replies_list_table = new BBP_Topic_Replies_List_Table();
+	$replies_list_table->prepare_items( $topic->ID ); ?>
+
+	<form id="bbp-topic-replies" method="get">
+		<input type="hidden" name="page" value="<?php echo esc_attr( $page ); ?>" />
+
+		<?php $replies_list_table->display(); ?>
+	</form>
+
+	<?php
+}
+
+/** Users *********************************************************************/
+
+/**
+ * Anonymous user information meta-box
+ *
+ * @since 2.0.0 bbPress (r2828)
+ *
+ * @param WP_Post $post The current post object
+ */
+function bbp_author_metabox( $post ) {
+
+	// Show extra bits if topic/reply is anonymous
+	if ( bbp_is_reply_anonymous( $post->ID ) || bbp_is_topic_anonymous( $post->ID ) ) : ?>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'Name:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_anonymous_name"><?php esc_html_e( 'Name', 'bbpress' ); ?></label>
+			<input type="text" id="bbp_anonymous_name" name="bbp_anonymous_name" value="<?php echo esc_attr( get_post_meta( $post->ID, '_bbp_anonymous_name', true ) ); ?>" />
+		</p>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'Email:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_anonymous_email"><?php esc_html_e( 'Email', 'bbpress' ); ?></label>
+			<input type="text" id="bbp_anonymous_email" name="bbp_anonymous_email" value="<?php echo esc_attr( get_post_meta( $post->ID, '_bbp_anonymous_email', true ) ); ?>" />
+		</p>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'Website:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_anonymous_website"><?php esc_html_e( 'Website', 'bbpress' ); ?></label>
+			<input type="text" id="bbp_anonymous_website" name="bbp_anonymous_website" value="<?php echo esc_attr( get_post_meta( $post->ID, '_bbp_anonymous_website', true ) ); ?>" />
+		</p>
+
+	<?php else : ?>
+
+		<p>
+			<strong class="label"><?php esc_html_e( 'ID:', 'bbpress' ); ?></strong>
+			<label class="screen-reader-text" for="bbp_author_id"><?php esc_html_e( 'ID', 'bbpress' ); ?></label>
+			<input type="text" id="bbp_author_id" name="post_author_override" value="<?php echo esc_attr( bbp_get_global_post_field( 'post_author' ) ); ?>" data-ajax-url="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_suggest_user' ), admin_url( 'admin-ajax.php', 'relative' ) ), 'bbp_suggest_user_nonce' ) ); ?>" />
+		</p>
+
+	<?php endif; ?>
+
+	<p>
+		<strong class="label"><?php esc_html_e( 'IP:', 'bbpress' ); ?></strong>
+		<label class="screen-reader-text" for="bbp_author_ip_address"><?php esc_html_e( 'IP Address', 'bbpress' ); ?></label>
+		<input type="text" id="bbp_author_ip_address" name="bbp_author_ip_address" value="<?php echo esc_attr( get_post_meta( $post->ID, '_bbp_author_ip', true ) ); ?>" disabled="disabled" />
+	</p>
+
+	<?php
+
+	do_action( 'bbp_author_metabox', $post );
+}
+
+/**
+ * Moderator assignment meta-box
+ *
+ * @since 2.6.0 bbPress (r2828)
+ */
+function bbp_moderator_assignment_metabox( $post ) {
+
+	// Get nicenames
+	$user_ids       = bbp_get_moderator_ids( $post->ID );
+	$user_nicenames = bbp_get_user_nicenames_from_ids( $user_ids );
+	$moderators     = ! empty( $user_nicenames )
+		? implode( ', ', array_map( 'esc_attr', $user_nicenames ) )
+		: ''; ?>
+
+	<label class="screen-reader-text" for="bbp_moderators"><?php esc_html_e( 'Moderators', 'bbpress' ); ?></label>
+	<input type="text" id="bbp_moderators" name="bbp_moderators" value="<?php echo esc_attr( $moderators ); ?>" />
+	<p class="howto"><?php esc_html_e( 'Separate user-names with commas', 'bbpress' ); ?></p>
+
+	<?php
+
+	do_action( 'bbp_moderator_assignment_metabox', $post );
+}
+
+/**
+ * See who engaged with a topic
+ *
+ * @since 2.6.0 bbPress (r6333)
+ */
+function bbp_topic_engagements_metabox( $post ) {
+
+	// Get user IDs
+	$user_ids = bbp_get_topic_engagements( $post->ID );
+
+	// Output
+	?><p><?php
+
+		// Relationships
+		$args = array(
+			'include' => $user_ids
+		);
+
+		// Users were found
+		if ( ! empty( $user_ids ) && bbp_has_users( $args ) ) :
+			bbp_metabox_user_links();
+
+		// No users
+		else :
+			esc_html_e( 'No users have engaged to this topic.', 'bbpress' );
+		endif;
+
+	?></p><?php
+
+	do_action( 'bbp_topic_engagements_metabox', $post );
+}
+
+/**
+ * See who marked a topic as a favorite
+ *
+ * @since 2.6.0 bbPress (r6197)
+ * @since 2.6.0 bbPress (r6333) Updated to use BBP_User_Query
+ */
+function bbp_topic_favorites_metabox( $post ) {
+
+	// Get user IDs
+	$user_ids = bbp_get_topic_favoriters( $post->ID );
+
+	// Output
+	?><p><?php
+
+		// Relationships
+		$args = array(
+			'include' => $user_ids
+		);
+
+		// Users were found
+		if ( ! empty( $user_ids ) && bbp_has_users( $args ) ) :
+			bbp_metabox_user_links();
+
+		// No users
+		else :
+			esc_html_e( 'No users have favorited this topic.', 'bbpress' );
+		endif;
+
+	?></p><?php
+
+	do_action( 'bbp_favorites_metabox', $post );
+}
+
+/**
+ * See who is subscribed to a topic
+ *
+ * @since 2.6.0 bbPress (r6197)
+ * @since 2.6.0 bbPress (r6333) Updated to use BBP_User_Query
+ */
+function bbp_topic_subscriptions_metabox( $post ) {
+
+	// Current user subscription
+	$input_value = bbp_is_user_subscribed( bbp_get_current_user_id(), $post->ID )
+		? 'bbp_subscribe' // maintain existing subscription
+		: '';             // do not add or remove subscription
+
+	// Get user IDs
+	$user_ids = bbp_get_subscribers( $post->ID );
+
+	// Output
+	?>
+	<input name="bbp_topic_subscription" id="bbp_topic_subscription" type="hidden" value="<?php echo esc_attr( $input_value ); ?>" />
+	<p><?php
+
+		// Relationships
+		$args = array(
+			'include' => $user_ids
+		);
+
+		// Users were found
+		if ( ! empty( $user_ids ) && bbp_has_users( $args ) ) :
+			bbp_metabox_user_links();
+
+		// No users
+		else :
+			esc_html_e( 'No users have subscribed to this topic.', 'bbpress' );
+		endif;
+
+	?></p><?php
+
+	do_action( 'bbp_subscriptions_metabox', $post );
+}
+
+/**
+ * See who is subscribed to a forum
+ *
+ * @since 2.6.0 bbPress (r6197)
+ * @since 2.6.0 bbPress (r6333) Updated to use BBP_User_Query
+ */
+function bbp_forum_subscriptions_metabox( $post ) {
+
+	// Get user IDs
+	$user_ids = bbp_get_subscribers( $post->ID );
+
+	// Output
+	?><p><?php
+
+		// Relationships
+		$args = array(
+			'include' => $user_ids
+		);
+
+		// Users were found
+		if ( ! empty( $user_ids ) && bbp_has_users( $args ) ) :
+			bbp_metabox_user_links();
+
+		// No users
+		else :
+			esc_html_e( 'No users have subscribed to this forum.', 'bbpress' );
+		endif;
+
+	?></p><?php
+
+	do_action( 'bbp_forum_subscriptions_metabox', $post );
+}
+
+/**
+ * Loop through queried metabox users, and output links to their avatars
+ *
+ * Developers Note: This function may change in a future release to include
+ * additional actions, so do not use this function in any third party plugin.
+ *
+ * @since 2.6.0 bbPress (r6913)
+ */
+function bbp_metabox_user_links() {
+
+	// Loop through users
+	while ( bbp_users() ) {
+
+		// Set the iterator
+		bbp_the_user();
+
+		// Get the user ID, URL, and Avatar
+		$user_id     = bbp_get_user_id();
+		$user_url    = bbp_get_user_profile_url( $user_id );
+		$user_avatar = get_avatar( $user_id, 32, '', '', array(
+			'force_display' => true
+		) );
+
+		// Output a link to the user avatar
+		echo '<a href="' . esc_url( $user_url ) . '">' . $user_avatar . '</a>';
+	}
+}
Index: /tags/2.6.6/src/includes/admin/parser.php
===================================================================
--- /tags/2.6.6/src/includes/admin/parser.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/parser.php	(revision 7162)
@@ -0,0 +1,2090 @@
+<?php
+
+/**
+ * bbPress BBCode Parser
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+/*
+This is a compressed copy of NBBC. Do not edit!
+
+Copyright (c) 2008-9, the Phantom Inker.  All rights reserved.
+Portions Copyright (c) 2004-2008 AddedBytes.com
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in
+  the documentation and/or other materials provided with the
+  distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE PHANTOM INKER "AS IS" AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+define("BBCODE_VERSION", "1.4.5");
+define("BBCODE_RELEASE", "2010-09-17");
+define("BBCODE_VERBATIM", 2);
+define("BBCODE_REQUIRED", 1);
+define("BBCODE_OPTIONAL", 0);
+define("BBCODE_PROHIBIT", -1);
+define("BBCODE_CHECK", 1);
+define("BBCODE_OUTPUT", 2);
+define("BBCODE_ENDTAG", 5);
+define("BBCODE_TAG", 4);
+define("BBCODE_TEXT", 3);
+define("BBCODE_NL", 2);
+define("BBCODE_WS", 1);
+define("BBCODE_EOI", 0);
+define("BBCODE_LEXSTATE_TEXT", 0);
+define("BBCODE_LEXSTATE_TAG", 1);
+define("BBCODE_MODE_SIMPLE", 0);
+define("BBCODE_MODE_CALLBACK", 1);
+define("BBCODE_MODE_INTERNAL", 2);
+define("BBCODE_MODE_LIBRARY", 3);
+define("BBCODE_MODE_ENHANCED", 4);
+define("BBCODE_STACK_TOKEN", 0);
+define("BBCODE_STACK_TEXT", 1);
+define("BBCODE_STACK_TAG", 2);
+define("BBCODE_STACK_CLASS", 3);
+if (!function_exists('str_split')) {
+function str_split($string, $split_length = 1) {
+$array = explode("\r\n", chunk_split($string, $split_length));
+array_pop($array);
+return $array;
+}
+}
+$BBCode_SourceDir = dirname(__FILE__);
+
+class BBCodeLexer {
+var $token;
+var $text;
+var $tag;
+var $state;
+var $input;
+var $ptr;
+var $unget;
+var $verbatim;
+var $debug;
+var $tagmarker;
+var $end_tagmarker;
+var $pat_main;
+var $pat_comment;
+var $pat_comment2;
+var $pat_wiki;
+function __construct($string, $tagmarker = '[') {
+$regex_beginmarkers = Array( '[' => '\[', '<' => '<', '{' => '\{', '(' => '\(' );
+$regex_endmarkers = Array( '[' => '\]', '<' => '>', '{' => '\}', '(' => '\)' );
+$endmarkers = Array( '[' => ']', '<' => '>', '{' => '}', '(' => ')' );
+if (!isset($regex_endmarkers[$tagmarker])) $tagmarker = '[';
+$e = $regex_endmarkers[$tagmarker];
+$b = $regex_beginmarkers[$tagmarker];
+$this->tagmarker = $tagmarker;
+$this->end_tagmarker = $endmarkers[$tagmarker];
+$this->pat_main = "/( "
+. "{$b}"
+. "(?! -- | ' | !-- | {$b}{$b} )"
+. "(?: [^\\n\\r{$b}{$e}] | \\\" [^\\\"\\n\\r]* \\\" | \\' [^\\'\\n\\r]* \\' )*"
+. "{$e}"
+. "| {$b}{$b} (?: [^{$e}\\r\\n] | {$e}[^{$e}\\r\\n] )* {$e}{$e}"
+. "| {$b} (?: -- | ' ) (?: [^{$e}\\n\\r]* ) {$e}"
+. "| {$b}!-- (?: [^-] | -[^-] | --[^{$e}] )* --{$e}"
+. "| -----+"
+. "| \\x0D\\x0A | \\x0A\\x0D | \\x0D | \\x0A"
+. "| [\\x00-\\x09\\x0B-\\x0C\\x0E-\\x20]+(?=[\\x0D\\x0A{$b}]|-----|$)"
+. "| (?<=[\\x0D\\x0A{$e}]|-----|^)[\\x00-\\x09\\x0B-\\x0C\\x0E-\\x20]+"
+. " )/Dx";
+$this->input = preg_split($this->pat_main, $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+$this->pat_comment = "/^ {$b} (?: -- | ' ) /Dx";
+$this->pat_comment2 = "/^ {$b}!-- (?: [^-] | -[^-] | --[^{$e}] )* --{$e} $/Dx";
+$this->pat_wiki = "/^ {$b}{$b} ([^\\|]*) (?:\\|(.*))? {$e}{$e} $/Dx";
+$this->ptr = 0;
+$this->unget = false;
+$this->state = BBCODE_LEXSTATE_TEXT;
+$this->verbatim = false;
+$this->token = BBCODE_EOI;
+$this->tag = false;
+$this->text = "";
+}
+function BBCodeLexer($string, $tagmarker = '[') {
+$this->__construct($string, $tagmarker);
+}
+function GuessTextLength() {
+$length = 0;
+$ptr = 0;
+$state = BBCODE_LEXSTATE_TEXT;
+while ($ptr < count($this->input)) {
+$text = $this->input[$ptr++];
+if ($state == BBCODE_LEXSTATE_TEXT) {
+$state = BBCODE_LEXSTATE_TAG;
+$length += strlen($text);
+}
+else {
+switch (ord(substr($this->text, 0, 1))) {
+case 10:
+case 13:
+$state = BBCODE_LEXSTATE_TEXT;
+$length++;
+break;
+default:
+$state = BBCODE_LEXSTATE_TEXT;
+$length += strlen($text);
+break;
+case 40:
+case 60:
+case 91:
+case 123:
+$state = BBCODE_LEXSTATE_TEXT;
+break;
+}
+}
+}
+return $length;
+}
+function NextToken() {
+if ($this->unget) {
+$this->unget = false;
+return $this->token;
+}
+while (true) {
+if ($this->ptr >= count($this->input)) {
+$this->text = "";
+$this->tag = false;
+return $this->token = BBCODE_EOI;
+}
+$this->text = preg_replace("/[\\x00-\\x08\\x0B-\\x0C\\x0E-\\x1F]/", "",
+$this->input[$this->ptr++]);
+if ($this->verbatim) {
+$this->tag = false;
+if ($this->state == BBCODE_LEXSTATE_TEXT) {
+$this->state = BBCODE_LEXSTATE_TAG;
+$token_type = BBCODE_TEXT;
+}
+else {
+$this->state = BBCODE_LEXSTATE_TEXT;
+switch (ord(substr($this->text, 0, 1))) {
+case 10:
+case 13:
+$token_type = BBCODE_NL;
+break;
+default:
+$token_type = BBCODE_WS;
+break;
+case 45:
+case 40:
+case 60:
+case 91:
+case 123:
+$token_type = BBCODE_TEXT;
+break;
+}
+}
+if (strlen($this->text) > 0)
+return $this->token = $token_type;
+}
+else if ($this->state == BBCODE_LEXSTATE_TEXT) {
+$this->state = BBCODE_LEXSTATE_TAG;
+$this->tag = false;
+if (strlen($this->text) > 0)
+return $this->token = BBCODE_TEXT;
+}
+else {
+switch (ord(substr($this->text, 0, 1))) {
+case 10:
+case 13:
+$this->tag = false;
+$this->state = BBCODE_LEXSTATE_TEXT;
+return $this->token = BBCODE_NL;
+case 45:
+if (preg_match("/^-----/", $this->text)) {
+$this->tag = Array('_name' => 'rule', '_endtag' => false, '_default' => '');
+$this->state = BBCODE_LEXSTATE_TEXT;
+return $this->token = BBCODE_TAG;
+}
+else {
+$this->tag = false;
+$this->state = BBCODE_LEXSTATE_TEXT;
+if (strlen($this->text) > 0)
+return $this->token = BBCODE_TEXT;
+continue 2;
+}
+default:
+$this->tag = false;
+$this->state = BBCODE_LEXSTATE_TEXT;
+return $this->token = BBCODE_WS;
+case 40:
+case 60:
+case 91:
+case 123:
+if (preg_match($this->pat_comment, $this->text)) {
+$this->state = BBCODE_LEXSTATE_TEXT;
+continue 2;
+}
+if (preg_match($this->pat_comment2, $this->text)) {
+$this->state = BBCODE_LEXSTATE_TEXT;
+continue 2;
+}
+if (preg_match($this->pat_wiki, $this->text, $matches)) {
+$this->tag = Array('_name' => 'wiki', '_endtag' => false,
+'_default' => @$matches[1], 'title' => @$matches[2]);
+$this->state = BBCODE_LEXSTATE_TEXT;
+return $this->token = BBCODE_TAG;
+}
+$this->tag = $this->Internal_DecodeTag($this->text);
+$this->state = BBCODE_LEXSTATE_TEXT;
+return $this->token = ($this->tag['_end'] ? BBCODE_ENDTAG : BBCODE_TAG);
+}
+}
+}
+}
+function UngetToken() {
+if ($this->token !== BBCODE_EOI)
+$this->unget = true;
+}
+function PeekToken() {
+$result = $this->NextToken();
+if ($this->token !== BBCODE_EOI)
+$this->unget = true;
+return $result;
+}
+function SaveState() {
+return Array(
+'token' => $this->token,
+'text' => $this->text,
+'tag' => $this->tag,
+'state' => $this->state,
+'input' => $this->input,
+'ptr' => $this->ptr,
+'unget' => $this->unget,
+'verbatim' => $this->verbatim
+);
+}
+function RestoreState($state) {
+if (!is_array($state)) return;
+$this->token = @$state['token'];
+$this->text = @$state['text'];
+$this->tag = @$state['tag'];
+$this->state = @$state['state'];
+$this->input = @$state['input'];
+$this->ptr = @$state['ptr'];
+$this->unget = @$state['unget'];
+$this->verbatim = @$state['verbatim'];
+}
+function Internal_StripQuotes($string) {
+if (preg_match("/^\\\"(.*)\\\"$/", $string, $matches))
+return $matches[1];
+else if (preg_match("/^\\'(.*)\\'$/", $string, $matches))
+return $matches[1];
+else return $string;
+}
+function Internal_ClassifyPiece($ptr, $pieces) {
+if ($ptr >= count($pieces)) return -1;
+$piece = $pieces[$ptr];
+if ($piece == '=') return '=';
+else if (preg_match("/^[\\'\\\"]/", $piece)) return '"';
+else if (preg_match("/^[\\x00-\\x20]+$/", $piece)) return ' ';
+else return 'A';
+}
+function Internal_DecodeTag($tag) {
+$result = Array('_tag' => $tag, '_endtag' => '', '_name' => '',
+'_hasend' => false, '_end' => false, '_default' => false);
+$tag = substr($tag, 1, strlen($tag)-2);
+$ch = ord(substr($tag, 0, 1));
+if ($ch >= 0 && $ch <= 32) return $result;
+$pieces = preg_split("/(\\\"[^\\\"]+\\\"|\\'[^\\']+\\'|=|[\\x00-\\x20]+)/",
+$tag, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+$ptr = 0;
+if (count($pieces) < 1) return $result;
+if (@substr($pieces[$ptr], 0, 1) == '/') {
+$result['_name'] = strtolower(substr($pieces[$ptr++], 1));
+$result['_end'] = true;
+}
+else {
+$result['_name'] = strtolower($pieces[$ptr++]);
+$result['_end'] = false;
+}
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) == ' ')
+$ptr++;
+$params = Array();
+if ($type != '=') {
+$result['_default'] = false;
+$params[] = Array('key' => '', 'value' => '');
+}
+else {
+$ptr++;
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) == ' ')
+$ptr++;
+if ($type == "\"")
+$value = $this->Internal_StripQuotes($pieces[$ptr++]);
+else {
+$after_space = false;
+$start = $ptr;
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) != -1) {
+if ($type == ' ') $after_space = true;
+if ($type == '=' && $after_space) break;
+$ptr++;
+}
+if ($type == -1) $ptr--;
+if ($type == '=') {
+$ptr--;
+while ($ptr > $start && $this->Internal_ClassifyPiece($ptr, $pieces) == ' ')
+$ptr--;
+while ($ptr > $start && $this->Internal_ClassifyPiece($ptr, $pieces) != ' ')
+$ptr--;
+}
+$value = "";
+for (; $start <= $ptr; $start++) {
+if ($this->Internal_ClassifyPiece($start, $pieces) == ' ')
+$value .= " ";
+else $value .= $this->Internal_StripQuotes($pieces[$start]);
+}
+$value = trim($value);
+$ptr++;
+}
+$result['_default'] = $value;
+$params[] = Array('key' => '', 'value' => $value);
+}
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) != -1) {
+while ($type == ' ') {
+$ptr++;
+$type = $this->Internal_ClassifyPiece($ptr, $pieces);
+}
+if ($type == 'A' || $type == '"')
+$key = strtolower($this->Internal_StripQuotes(@$pieces[$ptr++]));
+else if ($type == '=') {
+$ptr++;
+continue;
+}
+else if ($type == -1) break;
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) == ' ')
+$ptr++;
+if ($type != '=')
+$value = $this->Internal_StripQuotes($key);
+else {
+$ptr++;
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) == ' ')
+$ptr++;
+if ($type == '"') {
+$value = $this->Internal_StripQuotes($pieces[$ptr++]);
+}
+else if ($type != -1) {
+$value = $pieces[$ptr++];
+while (($type = $this->Internal_ClassifyPiece($ptr, $pieces)) != -1
+&& $type != ' ')
+$value .= $pieces[$ptr++];
+}
+else $value = "";
+}
+if (substr($key, 0, 1) != '_')
+$result[$key] = $value;
+$params[] = Array('key' => $key, 'value' => $value);
+}
+$result['_params'] = $params;
+return $result;
+}
+}
+
+class BBCodeLibrary {
+var $default_smileys = Array(
+':)' => 'smile.gif', ':-)' => 'smile.gif',
+'=)' => 'smile.gif', '=-)' => 'smile.gif',
+':(' => 'frown.gif', ':-(' => 'frown.gif',
+'=(' => 'frown.gif', '=-(' => 'frown.gif',
+':D' => 'bigsmile.gif', ':-D' => 'bigsmile.gif',
+'=D' => 'bigsmile.gif', '=-D' => 'bigsmile.gif',
+'>:('=> 'angry.gif', '>:-('=> 'angry.gif',
+'>=('=> 'angry.gif', '>=-('=> 'angry.gif',
+'D:' => 'angry.gif', 'D-:' => 'angry.gif',
+'D=' => 'angry.gif', 'D-=' => 'angry.gif',
+'>:)'=> 'evil.gif', '>:-)'=> 'evil.gif',
+'>=)'=> 'evil.gif', '>=-)'=> 'evil.gif',
+'>:D'=> 'evil.gif', '>:-D'=> 'evil.gif',
+'>=D'=> 'evil.gif', '>=-D'=> 'evil.gif',
+'>;)'=> 'sneaky.gif', '>;-)'=> 'sneaky.gif',
+'>;D'=> 'sneaky.gif', '>;-D'=> 'sneaky.gif',
+'O:)' => 'saint.gif', 'O:-)' => 'saint.gif',
+'O=)' => 'saint.gif', 'O=-)' => 'saint.gif',
+':O' => 'surprise.gif', ':-O' => 'surprise.gif',
+'=O' => 'surprise.gif', '=-O' => 'surprise.gif',
+':?' => 'confuse.gif', ':-?' => 'confuse.gif',
+'=?' => 'confuse.gif', '=-?' => 'confuse.gif',
+':s' => 'worry.gif', ':-S' => 'worry.gif',
+'=s' => 'worry.gif', '=-S' => 'worry.gif',
+':|' => 'neutral.gif', ':-|' => 'neutral.gif',
+'=|' => 'neutral.gif', '=-|' => 'neutral.gif',
+':I' => 'neutral.gif', ':-I' => 'neutral.gif',
+'=I' => 'neutral.gif', '=-I' => 'neutral.gif',
+':/' => 'irritated.gif', ':-/' => 'irritated.gif',
+'=/' => 'irritated.gif', '=-/' => 'irritated.gif',
+':\\' => 'irritated.gif', ':-\\' => 'irritated.gif',
+'=\\' => 'irritated.gif', '=-\\' => 'irritated.gif',
+':P' => 'tongue.gif', ':-P' => 'tongue.gif',
+'=P' => 'tongue.gif', '=-P' => 'tongue.gif',
+'X-P' => 'tongue.gif',
+'8)' => 'bigeyes.gif', '8-)' => 'bigeyes.gif',
+'B)' => 'cool.gif', 'B-)' => 'cool.gif',
+';)' => 'wink.gif', ';-)' => 'wink.gif',
+';D' => 'bigwink.gif', ';-D' => 'bigwink.gif',
+'^_^'=> 'anime.gif', '^^;' => 'sweatdrop.gif',
+'>_>'=> 'lookright.gif', '>.>' => 'lookright.gif',
+'<_<'=> 'lookleft.gif', '<.<' => 'lookleft.gif',
+'XD' => 'laugh.gif', 'X-D' => 'laugh.gif',
+';D' => 'bigwink.gif', ';-D' => 'bigwink.gif',
+':3' => 'smile3.gif', ':-3' => 'smile3.gif',
+'=3' => 'smile3.gif', '=-3' => 'smile3.gif',
+';3' => 'wink3.gif', ';-3' => 'wink3.gif',
+'<g>' => 'teeth.gif', '<G>' => 'teeth.gif',
+'o.O' => 'boggle.gif', 'O.o' => 'boggle.gif',
+':blue:' => 'blue.gif',
+':zzz:' => 'sleepy.gif',
+'<3' => 'heart.gif',
+':star:' => 'star.gif',
+);
+var $default_tag_rules = Array(
+'b' => Array(
+'simple_start' => "<b>",
+'simple_end' => "</b>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'plain_start' => "<b>",
+'plain_end' => "</b>",
+),
+'i' => Array(
+'simple_start' => "<i>",
+'simple_end' => "</i>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'plain_start' => "<i>",
+'plain_end' => "</i>",
+),
+'u' => Array(
+'simple_start' => "<u>",
+'simple_end' => "</u>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'plain_start' => "<u>",
+'plain_end' => "</u>",
+),
+'s' => Array(
+'simple_start' => "<strike>",
+'simple_end' => "</strike>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'plain_start' => "<i>",
+'plain_end' => "</i>",
+),
+'font' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'allow' => Array('_default' => '/^[a-zA-Z0-9._ -]+$/'),
+'method' => 'DoFont',
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'color' => Array(
+'mode' => BBCODE_MODE_ENHANCED,
+'allow' => Array('_default' => '/^#?[a-zA-Z0-9._ -]+$/'),
+'template' => '<span style="color:{$_default/tw}">{$_content/v}</span>',
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'size' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'allow' => Array('_default' => '/^[0-9.]+$/D'),
+'method' => 'DoSize',
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'sup' => Array(
+'simple_start' => "<sup>",
+'simple_end' => "</sup>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'sub' => Array(
+'simple_start' => "<sub>",
+'simple_end' => "</sub>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'spoiler' => Array(
+'simple_start' => "<span class=\"bbcode_spoiler\">",
+'simple_end' => "</span>",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'acronym' => Array(
+'mode' => BBCODE_MODE_ENHANCED,
+'template' => '<span class="bbcode_acronym" title="{$_default/e}">{$_content/v}</span>',
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+),
+'url' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => 'DoURL',
+'class' => 'link',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline'),
+'content' => BBCODE_REQUIRED,
+'plain_start' => "<a href=\"{\$link}\">",
+'plain_end' => "</a>",
+'plain_content' => Array('_content', '_default'),
+'plain_link' => Array('_default', '_content'),
+),
+'email' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => 'DoEmail',
+'class' => 'link',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline'),
+'content' => BBCODE_REQUIRED,
+'plain_start' => "<a href=\"mailto:{\$link}\">",
+'plain_end' => "</a>",
+'plain_content' => Array('_content', '_default'),
+'plain_link' => Array('_default', '_content'),
+),
+'wiki' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => "DoWiki",
+'class' => 'link',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline'),
+'end_tag' => BBCODE_PROHIBIT,
+'content' => BBCODE_PROHIBIT,
+'plain_start' => "<b>[",
+'plain_end' => "]</b>",
+'plain_content' => Array('title', '_default'),
+'plain_link' => Array('_default', '_content'),
+),
+'img' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => "DoImage",
+'class' => 'image',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'end_tag' => BBCODE_REQUIRED,
+'content' => BBCODE_REQUIRED,
+'plain_start' => "[image]",
+'plain_content' => Array(),
+),
+'rule' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => "DoRule",
+'class' => 'block',
+'allow_in' => Array('listitem', 'block', 'columns'),
+'end_tag' => BBCODE_PROHIBIT,
+'content' => BBCODE_PROHIBIT,
+'before_tag' => "sns",
+'after_tag' => "sns",
+'plain_start' => "\n-----\n",
+'plain_end' => "",
+'plain_content' => Array(),
+),
+'br' => Array(
+'mode' => BBCODE_MODE_SIMPLE,
+'simple_start' => "<br />\n",
+'simple_end' => "",
+'class' => 'inline',
+'allow_in' => Array('listitem', 'block', 'columns', 'inline', 'link'),
+'end_tag' => BBCODE_PROHIBIT,
+'content' => BBCODE_PROHIBIT,
+'before_tag' => "s",
+'after_tag' => "s",
+'plain_start' => "\n",
+'plain_end' => "",
+'plain_content' => Array(),
+),
+'left' => Array(
+'simple_start' => "\n<div class=\"bbcode_left\" style=\"text-align:left\">\n",
+'simple_end' => "\n</div>\n",
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'right' => Array(
+'simple_start' => "\n<div class=\"bbcode_right\" style=\"text-align:right\">\n",
+'simple_end' => "\n</div>\n",
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'center' => Array(
+'simple_start' => "\n<div class=\"bbcode_center\" style=\"text-align:center\">\n",
+'simple_end' => "\n</div>\n",
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'indent' => Array(
+'simple_start' => "\n<div class=\"bbcode_indent\" style=\"margin-left:4em\">\n",
+'simple_end' => "\n</div>\n",
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'columns' => Array(
+'simple_start' => "\n<table class=\"bbcode_columns\"><tbody><tr><td class=\"bbcode_column bbcode_firstcolumn\">\n",
+'simple_end' => "\n</td></tr></tbody></table>\n",
+'class' => 'columns',
+'allow_in' => Array('listitem', 'block', 'columns'),
+'end_tag' => BBCODE_REQUIRED,
+'content' => BBCODE_REQUIRED,
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'nextcol' => Array(
+'simple_start' => "\n</td><td class=\"bbcode_column\">\n",
+'class' => 'nextcol',
+'allow_in' => Array('columns'),
+'end_tag' => BBCODE_PROHIBIT,
+'content' => BBCODE_PROHIBIT,
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "",
+),
+'code' => Array(
+'mode' => BBCODE_MODE_ENHANCED,
+'template' => "\n<div class=\"bbcode_code\">\n<div class=\"bbcode_code_head\">Code:</div>\n<div class=\"bbcode_code_body\" style=\"white-space:pre\">{\$_content/v}</div>\n</div>\n",
+'class' => 'code',
+'allow_in' => Array('listitem', 'block', 'columns'),
+'content' => BBCODE_VERBATIM,
+'before_tag' => "sns",
+'after_tag' => "sn",
+'before_endtag' => "sn",
+'after_endtag' => "sns",
+'plain_start' => "\n<b>Code:</b>\n",
+'plain_end' => "\n",
+),
+'quote' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => "DoQuote",
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n<b>Quote:</b>\n",
+'plain_end' => "\n",
+),
+'list' => Array(
+'mode' => BBCODE_MODE_LIBRARY,
+'method' => 'DoList',
+'class' => 'list',
+'allow_in' => Array('listitem', 'block', 'columns'),
+'before_tag' => "sns",
+'after_tag' => "sns",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n",
+'plain_end' => "\n",
+),
+'*' => Array(
+'simple_start' => "<li>",
+'simple_end' => "</li>\n",
+'class' => 'listitem',
+'allow_in' => Array('list'),
+'end_tag' => BBCODE_OPTIONAL,
+'before_tag' => "s",
+'after_tag' => "s",
+'before_endtag' => "sns",
+'after_endtag' => "sns",
+'plain_start' => "\n * ",
+'plain_end' => "\n",
+),
+);
+function DoURL($bbcode, $action, $name, $default, $params, $content) {
+if ($action == BBCODE_CHECK) return true;
+$url = is_string($default) ? $default : $bbcode->UnHTMLEncode(strip_tags($content));
+if ($bbcode->IsValidURL($url)) {
+if ($bbcode->debug)
+print "ISVALIDURL<br />";
+if ($bbcode->url_targetable !== false && isset($params['target']))
+$target = " target=\"" . htmlspecialchars($params['target']) . "\"";
+else $target = "";
+if ($bbcode->url_target !== false)
+if (!($bbcode->url_targetable == 'override' && isset($params['target'])))
+$target = " target=\"" . htmlspecialchars($bbcode->url_target) . "\"";
+return '<a href="' . htmlspecialchars($url) . '" class="bbcode_url"' . $target . '>' . $content . '</a>';
+}
+else return htmlspecialchars($params['_tag']) . $content . htmlspecialchars($params['_endtag']);
+}
+function DoEmail($bbcode, $action, $name, $default, $params, $content) {
+if ($action == BBCODE_CHECK) return true;
+$email = is_string($default) ? $default : $bbcode->UnHTMLEncode(strip_tags($content));
+if ($bbcode->IsValidEmail($email))
+return '<a href="mailto:' . htmlspecialchars($email) . '" class="bbcode_email">' . $content . '</a>';
+else return htmlspecialchars($params['_tag']) . $content . htmlspecialchars($params['_endtag']);
+}
+function DoSize($bbcode, $action, $name, $default, $params, $content) {
+switch ($default) {
+case '0': $size = '.5em'; break;
+case '1': $size = '.67em'; break;
+case '2': $size = '.83em'; break;
+default:
+case '3': $size = '1.0em'; break;
+case '4': $size = '1.17em'; break;
+case '5': $size = '1.5em'; break;
+case '6': $size = '2.0em'; break;
+case '7': $size = '2.5em'; break;
+}
+return "<span style=\"font-size:$size\">$content</span>";
+}
+function DoFont($bbcode, $action, $name, $default, $params, $content) {
+$fonts = explode(",", $default);
+$result = "";
+$special_fonts = Array(
+'serif' => 'serif',
+'sans-serif' => 'sans-serif',
+'sans serif' => 'sans-serif',
+'sansserif' => 'sans-serif',
+'sans' => 'sans-serif',
+'cursive' => 'cursive',
+'fantasy' => 'fantasy',
+'monospace' => 'monospace',
+'mono' => 'monospace',
+);
+foreach ($fonts as $font) {
+$font = trim($font);
+if (isset($special_fonts[$font])) {
+if (strlen($result) > 0) $result .= ",";
+$result .= $special_fonts[$font];
+}
+else if (strlen($font) > 0) {
+if (strlen($result) > 0) $result .= ",";
+$result .= "'$font'";
+}
+}
+return "<span style=\"font-family:$result\">$content</span>";
+}
+function DoWiki($bbcode, $action, $name, $default, $params, $content) {
+$name = $bbcode->Wikify($default);
+if ($action == BBCODE_CHECK)
+return strlen($name) > 0;
+$title = trim(@$params['title']);
+if (strlen($title) <= 0) $title = trim($default);
+return "<a href=\"{$bbcode->wiki_url}$name\" class=\"bbcode_wiki\">"
+. htmlspecialchars($title) . "</a>";
+}
+function DoImage($bbcode, $action, $name, $default, $params, $content) {
+if ($action == BBCODE_CHECK) return true;
+$content = trim($bbcode->UnHTMLEncode(strip_tags($content)));
+if (preg_match("/\\.(?:gif|jpeg|jpg|jpe|png)$/", $content)) {
+if (preg_match("/^[a-zA-Z0-9_][^:]+$/", $content)) {
+if (!preg_match("/(?:\\/\\.\\.\\/)|(?:^\\.\\.\\/)|(?:^\\/)/", $content)) {
+$info = @getimagesize("{$bbcode->local_img_dir}/{$content}");
+if ($info[2] == IMAGETYPE_GIF || $info[2] == IMAGETYPE_JPEG || $info[2] == IMAGETYPE_PNG) {
+return "<img src=\""
+. htmlspecialchars("{$bbcode->local_img_url}/{$content}") . "\" alt=\""
+. htmlspecialchars(basename($content)) . "\" width=\""
+. htmlspecialchars($info[0]) . "\" height=\""
+. htmlspecialchars($info[1]) . "\" class=\"bbcode_img\" />";
+}
+}
+}
+else if ($bbcode->IsValidURL($content, false)) {
+return "<img src=\"" . htmlspecialchars($content) . "\" alt=\""
+. htmlspecialchars(basename($content)) . "\" class=\"bbcode_img\" />";
+}
+}
+return htmlspecialchars($params['_tag']) . htmlspecialchars($content) . htmlspecialchars($params['_endtag']);
+}
+function DoRule($bbcode, $action, $name, $default, $params, $content) {
+if ($action == BBCODE_CHECK) return true;
+else return $bbcode->rule_html;
+}
+function DoQuote($bbcode, $action, $name, $default, $params, $content) {
+if ($action == BBCODE_CHECK) return true;
+if (isset($params['name'])) {
+$title = htmlspecialchars(trim($params['name'])) . " wrote";
+if (isset($params['date']))
+$title .= " on " . htmlspecialchars(trim($params['date']));
+$title .= ":";
+if (isset($params['url'])) {
+$url = trim($params['url']);
+if ($bbcode->IsValidURL($url))
+$title = "<a href=\"" . htmlspecialchars($params['url']) . "\">" . $title . "</a>";
+}
+}
+else if (!is_string($default))
+$title = "Quote:";
+else $title = htmlspecialchars(trim($default)) . " wrote:";
+return "\n<div class=\"bbcode_quote\">\n<div class=\"bbcode_quote_head\">"
+. $title . "</div>\n<div class=\"bbcode_quote_body\">"
+. $content . "</div>\n</div>\n";
+}
+function DoList($bbcode, $action, $name, $default, $params, $content) {
+$list_styles = Array(
+'1' => 'decimal',
+'01' => 'decimal-leading-zero',
+'i' => 'lower-roman',
+'I' => 'upper-roman',
+'a' => 'lower-alpha',
+'A' => 'upper-alpha',
+);
+$ci_list_styles = Array(
+'circle' => 'circle',
+'disc' => 'disc',
+'square' => 'square',
+'greek' => 'lower-greek',
+'armenian' => 'armenian',
+'georgian' => 'georgian',
+);
+$ul_types = Array(
+'circle' => 'circle',
+'disc' => 'disc',
+'square' => 'square',
+);
+$default = trim($default);
+if ($action == BBCODE_CHECK) {
+if (!is_string($default) || strlen($default) == "") return true;
+else if (isset($list_styles[$default])) return true;
+else if (isset($ci_list_styles[strtolower($default)])) return true;
+else return false;
+}
+if (!is_string($default) || strlen($default) == "") {
+$elem = 'ul';
+$type = '';
+}
+else if ($default == '1') {
+$elem = 'ol';
+$type = '';
+}
+else if (isset($list_styles[$default])) {
+$elem = 'ol';
+$type = $list_styles[$default];
+}
+else {
+$default = strtolower($default);
+if (isset($ul_types[$default])) {
+$elem = 'ul';
+$type = $ul_types[$default];
+}
+else if (isset($ci_list_styles[$default])) {
+$elem = 'ol';
+$type = $ci_list_styles[$default];
+}
+}
+if (strlen($type))
+return "\n<$elem class=\"bbcode_list\" style=\"list-style-type:$type\">\n$content</$elem>\n";
+else return "\n<$elem class=\"bbcode_list\">\n$content</$elem>\n";
+}
+}
+
+class BBCodeEmailAddressValidator {
+function check_email_address($strEmailAddress) {
+if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $strEmailAddress)) {
+return false;
+}
+$intAtSymbol = strrpos($strEmailAddress, '@');
+if ($intAtSymbol === false) {
+return false;
+}
+$arrEmailAddress[0] = substr($strEmailAddress, 0, $intAtSymbol);
+$arrEmailAddress[1] = substr($strEmailAddress, $intAtSymbol + 1);
+$arrTempAddress[0] = preg_replace('/"[^"]+"/'
+,''
+,$arrEmailAddress[0]);
+$arrTempAddress[1] = $arrEmailAddress[1];
+$strTempAddress = $arrTempAddress[0] . $arrTempAddress[1];
+if (strrpos($strTempAddress, '@') !== false) {
+return false;
+}
+if (!$this->check_local_portion($arrEmailAddress[0])) {
+return false;
+}
+if (!$this->check_domain_portion($arrEmailAddress[1])) {
+return false;
+}
+return true;
+}
+function check_local_portion($strLocalPortion) {
+if (!$this->check_text_length($strLocalPortion, 1, 64)) {
+return false;
+}
+$arrLocalPortion = explode('.', $strLocalPortion);
+for ($i = 0, $max = sizeof($arrLocalPortion); $i < $max; $i++) {
+if (!preg_match('.^('
+. '([A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]'
+. '[A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]{0,63})'
+.'|'
+. '("[^\\\"]{0,62}")'
+.')$.'
+,$arrLocalPortion[$i])) {
+return false;
+}
+}
+return true;
+}
+function check_domain_portion($strDomainPortion) {
+if (!$this->check_text_length($strDomainPortion, 1, 255)) {
+return false;
+}
+if (preg_match('/^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'
+.'(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}$/'
+,$strDomainPortion) ||
+preg_match('/^\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'
+.'(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]$/'
+,$strDomainPortion)) {
+return true;
+} else {
+$arrDomainPortion = explode('.', $strDomainPortion);
+if (sizeof($arrDomainPortion) < 2) {
+return false;
+}
+for ($i = 0, $max = sizeof($arrDomainPortion); $i < $max; $i++) {
+if (!$this->check_text_length($arrDomainPortion[$i], 1, 63)) {
+return false;
+}
+if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|'
+.'([A-Za-z0-9]+))$/', $arrDomainPortion[$i])) {
+return false;
+}
+}
+}
+return true;
+}
+function check_text_length($strText, $intMinimum, $intMaximum) {
+$intTextLength = strlen($strText);
+if (($intTextLength < $intMinimum) || ($intTextLength > $intMaximum)) {
+return false;
+} else {
+return true;
+}
+}
+}
+
+class BBCode {
+var $tag_rules;
+var $defaults;
+var $current_class;
+var $root_class;
+var $lost_start_tags;
+var $start_tags;
+var $allow_ampersand;
+var $tag_marker;
+var $ignore_newlines;
+var $plain_mode;
+var $detect_urls;
+var $url_pattern;
+var $output_limit;
+var $text_length;
+var $was_limited;
+var $limit_tail;
+var $limit_precision;
+var $smiley_dir;
+var $smiley_url;
+var $smileys;
+var $smiley_regex;
+var $enable_smileys;
+var $wiki_url;
+var $local_img_dir;
+var $local_img_url;
+var $url_targetable;
+var $url_target;
+var $rule_html;
+var $pre_trim;
+var $post_trim;
+var $debug;
+
+
+/* ADDED */
+// singleton instance
+private static $instance;
+
+// private constructor function
+// to prevent external instantiation
+private function __construct()
+{
+	$this->defaults = new BBCodeLibrary;
+	$this->tag_rules = $this->defaults->default_tag_rules;
+	$this->smileys = $this->defaults->default_smileys;
+	$this->enable_smileys = true;
+	$this->smiley_regex = false;
+	$this->smiley_dir = $this->GetDefaultSmileyDir();
+	$this->smiley_url = $this->GetDefaultSmileyURL();
+	$this->wiki_url = $this->GetDefaultWikiURL();
+	$this->local_img_dir = $this->GetDefaultLocalImgDir();
+	$this->local_img_url = $this->GetDefaultLocalImgURL();
+	$this->rule_html = $this->GetDefaultRuleHTML();
+	$this->pre_trim = "";
+	$this->post_trim = "";
+	$this->root_class = 'block';
+	$this->lost_start_tags = Array();
+	$this->start_tags = Array();
+	$this->tag_marker = '[';
+	$this->allow_ampsersand = false;
+	$this->current_class = $this->root_class;
+	$this->debug = false;
+	$this->ignore_newlines = false;
+	$this->output_limit = 0;
+	$this->plain_mode = false;
+	$this->was_limited = false;
+	$this->limit_tail = "...";
+	$this->limit_precision = 0.15;
+	$this->detect_urls = false;
+	$this->url_pattern = '<a href="{$url/h}">{$text/h}</a>';
+	$this->url_targetable = false;
+	$this->url_target = false;
+}
+
+// getInstance method
+public static function getInstance()
+{
+    if(!self::$instance)
+    {
+      self::$instance = new self();
+    }
+
+    return self::$instance;
+}
+/* ADDED */
+
+
+function SetPreTrim($trim = "a") { $this->pre_trim = $trim; }
+function GetPreTrim() { return $this->pre_trim; }
+function SetPostTrim($trim = "a") { $this->post_trim = $trim; }
+function GetPostTrim() { return $this->post_trim; }
+function SetRoot($class = 'block') { $this->root_class = $class; }
+function SetRootInline() { $this->root_class = 'inline'; }
+function SetRootBlock() { $this->root_class = 'block'; }
+function GetRoot() { return $this->root_class; }
+function SetDebug($enable = true) { $this->debug = $enable; }
+function GetDebug() { return $this->debug; }
+function SetAllowAmpersand($enable = true) { $this->allow_ampersand = $enable; }
+function GetAllowAmpersand() { return $this->allow_ampersand; }
+function SetTagMarker($marker = '[') { $this->tag_marker = $marker; }
+function GetTagMarker() { return $this->tag_marker; }
+function SetIgnoreNewlines($ignore = true) { $this->ignore_newlines = $ignore; }
+function GetIgnoreNewlines() { return $this->ignore_newlines; }
+function SetLimit($limit = 0) { $this->output_limit = $limit; }
+function GetLimit() { return $this->output_limit; }
+function SetLimitTail($tail = "...") { $this->limit_tail = $tail; }
+function GetLimitTail() { return $this->limit_tail; }
+function SetLimitPrecision($prec = 0.15) { $this->limit_precision = $prec; }
+function GetLimitPrecision() { return $this->limit_precision; }
+function WasLimited() { return $this->was_limited; }
+function SetPlainMode($enable = true) { $this->plain_mode = $enable; }
+function GetPlainMode() { return $this->plain_mode; }
+function SetDetectURLs($enable = true) { $this->detect_urls = $enable; }
+function GetDetectURLs() { return $this->detect_urls; }
+function SetURLPattern($pattern) { $this->url_pattern = $pattern; }
+function GetURLPattern() { return $this->url_pattern; }
+function SetURLTargetable($enable) { $this->url_targetable = $enable; }
+function GetURLTargetable() { return $this->url_targetable; }
+function SetURLTarget($target) { $this->url_target = $target; }
+function GetURLTarget() { return $this->url_target; }
+function AddRule($name, $rule) { $this->tag_rules[$name] = $rule; }
+function RemoveRule($name) { unset($this->tag_rules[$name]); }
+function GetRule($name) { return isset($this->tag_rules[$name])
+? $this->tag_rules[$name] : false; }
+function ClearRules() { $this->tag_rules = Array(); }
+function GetDefaultRule($name) { return isset($this->defaults->default_tag_rules[$name])
+? $this->defaults->default_tag_rules[$name] : false; }
+function SetDefaultRule($name) { if (isset($this->defaults->default_tag_rules[$name]))
+$this->AddRule($name, $this->defaults->default_tag_rules[$name]);
+else $this->RemoveRule($name); }
+function GetDefaultRules() { return $this->defaults->default_tag_rules; }
+function SetDefaultRules() { $this->tag_rules = $this->defaults->default_tag_rules; }
+function SetWikiURL($url) { $this->wiki_url = $url; }
+function GetWikiURL($url) { return $this->wiki_url; }
+function GetDefaultWikiURL() { return '/?page='; }
+function SetLocalImgDir($path) { $this->local_img_dir = $path; }
+function GetLocalImgDir() { return $this->local_img_dir; }
+function GetDefaultLocalImgDir() { return "img"; }
+function SetLocalImgURL($path) { $this->local_img_url = $path; }
+function GetLocalImgURL() { return $this->local_img_url; }
+function GetDefaultLocalImgURL() { return "img"; }
+function SetRuleHTML($html) { $this->rule_html = $html; }
+function GetRuleHTML() { return $this->rule_html; }
+function GetDefaultRuleHTML() { return "\n<hr class=\"bbcode_rule\" />\n"; }
+function AddSmiley($code, $image) { $this->smileys[$code] = $image; $this->smiley_regex = false; }
+function RemoveSmiley($code) { unset($this->smileys[$code]); $this->smiley_regex = false; }
+function GetSmiley($code) { return isset($this->smileys[$code])
+? $this->smileys[$code] : false; }
+function ClearSmileys() { $this->smileys = Array(); $this->smiley_regex = false; }
+function GetDefaultSmiley($code) { return isset($this->defaults->default_smileys[$code])
+? $this->defaults->default_smileys[$code] : false; }
+function SetDefaultSmiley($code) { $this->smileys[$code] = @$this->defaults->default_smileys[$code];
+$this->smiley_regex = false; }
+function GetDefaultSmileys() { return $this->defaults->default_smileys; }
+function SetDefaultSmileys() { $this->smileys = $this->defaults->default_smileys;
+$this->smiley_regex = false; }
+function SetSmileyDir($path) { $this->smiley_dir = $path; }
+function GetSmileyDir() { return $this->smiley_dir; }
+function GetDefaultSmileyDir() { return "smileys"; }
+function SetSmileyURL($path) { $this->smiley_url = $path; }
+function GetSmileyURL() { return $this->smiley_url; }
+function GetDefaultSmileyURL() { return "smileys"; }
+function SetEnableSmileys($enable = true) { $this->enable_smileys = $enable; }
+function GetEnableSmileys() { return $this->enable_smileys; }
+function nl2br($string) {
+return preg_replace("/\\x0A|\\x0D|\\x0A\\x0D|\\x0D\\x0A/", "<br />\n", $string);
+}
+function UnHTMLEncode($string) {
+if (function_exists("html_entity_decode"))
+return html_entity_decode($string);
+$string = preg_replace_callback('~&#x([0-9a-f]+);~i', array( $this, '_UnHTMLEncode_chr_callback'), $string);
+$string = preg_replace_callback('~&#([0-9]+);~', array($this, '_UnHTMLEncode_chr_hexdec_callback'), $string);
+$trans_tbl = get_html_translation_table(HTML_ENTITIES);
+$trans_tbl = array_flip($trans_tbl);
+return strtr($string, $trans_tbl);
+}
+function _UnHTMLEncode_chr_callback($match) {
+return chr(hexdec($match[1]));
+}
+function _UnHTMLEncode_chr_hexdec_callback($match) {
+return chr(hexdec($match[1]));
+}
+function Wikify($string) {
+return rawurlencode(str_replace(" ", "_",
+trim(preg_replace("/[!?;@#\$%\\^&*<>=+`~\\x00-\\x20_-]+/", " ", $string))));
+}
+function IsValidURL($string, $email_too = true) {
+if (preg_match("/^
+(?:https?|ftp):\\/\\/
+(?:
+(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+
+[a-zA-Z0-9]
+(?:[a-zA-Z0-9-]*[a-zA-Z0-9])?
+|
+\\[
+(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}
+(?:
+25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:
+(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21-\\x5A\\x53-\\x7F]
+|\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])+
+)
+\\]
+)
+(?::[0-9]{1,5})?
+(?:[\\/\\?\\#][^\\n\\r]*)?
+$/Dx", $string)) return true;
+if (preg_match("/^[^:]+([\\/\\\\?#][^\\r\\n]*)?$/D", $string))
+return true;
+if ($email_too)
+if (substr($string, 0, 7) == "mailto:")
+return $this->IsValidEmail(substr($string, 7));
+return false;
+}
+function IsValidEmail($string) {
+$validator = new BBCodeEmailAddressValidator;
+return $validator->check_email_address($string);
+/*
+return preg_match("/^
+(?:
+[a-z0-9\\!\\#\\\$\\%\\&\\'\\*\\+\\/=\\?\\^_`\\{\\|\\}~-]+
+(?:\.[a-z0-9\\!\\#\\\$\\%\\&\\'\\*\\+\\/=\\?\\^_`\\{\\|\\}~-]+)*
+|
+\"(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]
+|\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])*\"
+)
+@
+(?:
+(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+
+[a-z0-9]
+(?:[a-z0-9-]*[a-z0-9])?
+|
+\\[
+(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}
+(?:
+25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:
+(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21-\\x5A\\x53-\\x7F]
+|\\\\[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F])+
+)
+\\]
+)
+$/Dx", $string);
+*/
+}
+function HTMLEncode($string) {
+if (!$this->allow_ampersand)
+return htmlspecialchars($string);
+else return str_replace(Array('<', '>', '"'),
+Array('&lt;', '&gt;', '&quot;'), $string);
+}
+function FixupOutput($string) {
+if (!$this->detect_urls) {
+$output = $this->Internal_ProcessSmileys($string);
+}
+else {
+$chunks = $this->Internal_AutoDetectURLs($string);
+$output = Array();
+if (count($chunks)) {
+$is_a_url = false;
+foreach ($chunks as $index => $chunk) {
+if (!$is_a_url) {
+$chunk = $this->Internal_ProcessSmileys($chunk);
+}
+$output[] = $chunk;
+$is_a_url = !$is_a_url;
+}
+}
+$output = implode("", $output);
+}
+return $output;
+}
+function Internal_ProcessSmileys($string) {
+if (!$this->enable_smileys || $this->plain_mode) {
+$output = $this->HTMLEncode($string);
+}
+else {
+if ($this->smiley_regex === false) {
+$this->Internal_RebuildSmileys();
+}
+$tokens = preg_split($this->smiley_regex, $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+if (count($tokens) <= 1) {
+$output = $this->HTMLEncode($string);
+}
+else {
+$output = "";
+$is_a_smiley = false;
+foreach ($tokens as $token) {
+if (!$is_a_smiley) {
+$output .= $this->HTMLEncode($token);
+}
+else {
+if (isset($this->smiley_info[$token])) {
+$info = $this->smiley_info[$token];
+}
+else {
+$info = @getimagesize($this->smiley_dir . '/' . $this->smileys[$token]);
+$this->smiley_info[$token] = $info;
+}
+$alt = htmlspecialchars($token);
+$output .= "<img src=\"" . htmlspecialchars($this->smiley_url . '/' . $this->smileys[$token])
+. "\" width=\"{$info[0]}\" height=\"{$info[1]}\""
+. " alt=\"$alt\" title=\"$alt\" class=\"bbcode_smiley\" />";
+}
+$is_a_smiley = !$is_a_smiley;
+}
+}
+}
+return $output;
+}
+function Internal_RebuildSmileys() {
+$regex = Array("/(?<![\\w])(");
+$first = true;
+foreach ($this->smileys as $code => $filename) {
+if (!$first) $regex[] = "|";
+$regex[] = preg_quote("$code", '/');
+$first = false;
+}
+$regex[] = ")(?![\\w])/";
+$this->smiley_regex = implode("", $regex);
+}
+function Internal_AutoDetectURLs($string) {
+$output = preg_split("/( (?:
+(?:https?|ftp) : \\/*
+(?:
+(?: (?: [a-zA-Z0-9-]{2,} \\. )+
+(?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2}
+| aero | biz | coop | info | museum | name | pro
+| example | invalid | localhost | test | local | onion | swift ) )
+| (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} )
+| (?: [0-9A-Fa-f:]+ : [0-9A-Fa-f]{1,4} )
+)
+(?: : [0-9]+ )?
+(?! [a-zA-Z0-9.:-] )
+(?:
+\\/
+[^&?#\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]*
+)?
+(?:
+[?#]
+[^\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]+
+)?
+) | (?:
+(?:
+(?: (?: [a-zA-Z0-9-]{2,} \\. )+
+(?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2}
+| aero | biz | coop | info | museum | name | pro
+| example | invalid | localhost | test | local | onion | swift ) )
+| (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} )
+)
+(?: : [0-9]+ )?
+(?! [a-zA-Z0-9.:-] )
+(?:
+\\/
+[^&?#\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]*
+)?
+(?:
+[?#]
+[^\\(\\)\\[\\]\\{\\}<>\\'\\\"\\x00-\\x20\\x7F-\\xFF]+
+)?
+) | (?:
+[a-zA-Z0-9._-]{2,} @
+(?:
+(?: (?: [a-zA-Z0-9-]{2,} \\. )+
+(?: arpa | com | org | net | edu | gov | mil | int | [a-z]{2}
+| aero | biz | coop | info | museum | name | pro
+| example | invalid | localhost | test | local | onion | swift ) )
+| (?: [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} \\. [0-9]{1,3} )
+)
+) )/Dx", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+if (count($output) > 1) {
+$is_a_url = false;
+foreach ($output as $index => $token) {
+if ($is_a_url) {
+if (preg_match("/^[a-zA-Z0-9._-]{2,}@/", $token)) {
+$url = "mailto:" . $token;
+}
+else if (preg_match("/^(https?:|ftp:)\\/*([^\\/&?#]+)\\/*(.*)\$/", $token, $matches)) {
+$url = $matches[1] . '/' . '/' . $matches[2] . "/" . $matches[3];
+}
+else {
+preg_match("/^([^\\/&?#]+)\\/*(.*)\$/", $token, $matches);
+$url = "http:/" . "/" . $matches[1] . "/" . $matches[2];
+}
+$params = @parse_url($url);
+if (!is_array($params)) $params = Array();
+$params['url'] = $url;
+$params['link'] = $url;
+$params['text'] = $token;
+$output[$index] = $this->FillTemplate($this->url_pattern, $params);
+}
+$is_a_url = !$is_a_url;
+}
+}
+return $output;
+}
+function FillTemplate($template, $insert_array, $default_array = Array()) {
+$pieces = preg_split('/(\{\$[a-zA-Z0-9_.:\/-]+\})/', $template,
+-1, PREG_SPLIT_DELIM_CAPTURE);
+if (count($pieces) <= 1)
+return $template;
+$result = Array();
+$is_an_insert = false;
+foreach ($pieces as $piece) {
+if (!$is_an_insert) {
+$result[] = $piece;
+}
+else if (!preg_match('/\{\$([a-zA-Z0-9_:-]+)((?:\\.[a-zA-Z0-9_:-]+)*)(?:\/([a-zA-Z0-9_:-]+))?\}/', $piece, $matches)) {
+$result[] = $piece;
+}
+else {
+if (isset($insert_array[$matches[1]]))
+$value = @$insert_array[$matches[1]];
+else $value = @$default_array[$matches[1]];
+if (strlen(@$matches[2])) {
+foreach (explode(".", substr($matches[2], 1)) as $index) {
+if (is_array($value))
+$value = @$value[$index];
+else if (is_object($value)) {
+$value = (array)$value;
+$value = @$value[$index];
+}
+else $value = "";
+}
+}
+switch (gettype($value)) {
+case 'boolean': $value = $value ? "true" : "false"; break;
+case 'integer': $value = (string)$value; break;
+case 'double': $value = (string)$value; break;
+case 'string': break;
+default: $value = ""; break;
+}
+if (strlen(@$matches[3]))
+$flags = array_flip(str_split($matches[3]));
+else $flags = Array();
+if (!isset($flags['v'])) {
+if (isset($flags['w']))
+$value = preg_replace("/[\\x00-\\x09\\x0B-\x0C\x0E-\\x20]+/", " ", $value);
+if (isset($flags['t'])) $value = trim($value);
+if (isset($flags['b'])) $value = basename($value);
+if (isset($flags['e'])) $value = $this->HTMLEncode($value);
+else if (isset($flags['k'])) $value = $this->Wikify($value);
+else if (isset($flags['h'])) $value = htmlspecialchars($value);
+else if (isset($flags['u'])) $value = urlencode($value);
+if (isset($flags['n'])) $value = $this->nl2br($value);
+}
+$result[] = $value;
+}
+$is_an_insert = !$is_an_insert;
+}
+return implode("", $result);
+}
+function Internal_CollectText($array, $start = 0) {
+ob_start();
+for ($start = intval($start), $end = count($array); $start < $end; $start++)
+print $array[$start][BBCODE_STACK_TEXT];
+$output = ob_get_contents();
+ob_end_clean();
+return $output;
+}
+function Internal_CollectTextReverse($array, $start = 0, $end = 0) {
+ob_start();
+for ($start = intval($start); $start >= $end; $start--)
+print $array[$start][BBCODE_STACK_TEXT];
+$output = ob_get_contents();
+ob_end_clean();
+return $output;
+}
+function Internal_GenerateOutput($pos) {
+$output = Array();
+while (count($this->stack) > $pos) {
+$token = array_pop($this->stack);
+if ($token[BBCODE_STACK_TOKEN] != BBCODE_TAG) {
+$output[] = $token;
+}
+else {
+$name = @$token[BBCODE_STACK_TAG]['_name'];
+$rule = @$this->tag_rules[$name];
+$end_tag = @$rule['end_tag'];
+if (!isset($rule['end_tag'])) $end_tag = BBCODE_REQUIRED;
+else $end_tag = $rule['end_tag'];
+array_pop($this->start_tags[$name]);
+if ($end_tag == BBCODE_PROHIBIT) {
+$output[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_TEXT => $token[BBCODE_STACK_TEXT],
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+else {
+if ($end_tag == BBCODE_REQUIRED)
+@$this->lost_start_tags[$name] += 1;
+$end = $this->Internal_CleanupWSByIteratingPointer(@$rule['before_endtag'], 0, $output);
+$this->Internal_CleanupWSByPoppingStack(@$rule['after_tag'], $output);
+$tag_body = $this->Internal_CollectTextReverse($output, count($output)-1, $end);
+$this->Internal_CleanupWSByPoppingStack(@$rule['before_tag'], $this->stack);
+@$token[BBCODE_STACK_TAG]=@$this->Internal_UpdateParamsForMissingEndTag(@$token[BBCODE_STACK_TAG]);
+$tag_output = $this->DoTag(BBCODE_OUTPUT, $name,
+@$token[BBCODE_STACK_TAG]['_default'], @$token[BBCODE_STACK_TAG], $tag_body);
+$output = Array(Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_TEXT => $tag_output,
+BBCODE_STACK_CLASS => $this->current_class
+));
+}
+}
+}
+$this->Internal_ComputeCurrentClass();
+return $output;
+}
+function Internal_RewindToClass($class_list) {
+$pos = count($this->stack) - 1;
+while ($pos >= 0 && !in_array($this->stack[$pos][BBCODE_STACK_CLASS], $class_list))
+$pos--;
+if ($pos < 0) {
+if (!in_array($this->root_class, $class_list))
+return false;
+}
+$output = $this->Internal_GenerateOutput($pos+1);
+while (count($output)) {
+$token = array_pop($output);
+$token[BBCODE_STACK_CLASS] = $this->current_class;
+$this->stack[] = $token;
+}
+return true;
+}
+function Internal_FinishTag($tag_name) {
+if (strlen($tag_name) <= 0)
+return false;
+if (isset($this->start_tags[$tag_name])
+&& count($this->start_tags[$tag_name]))
+$pos = array_pop($this->start_tags[$tag_name]);
+else $pos = -1;
+if ($pos < 0) return false;
+$newpos = $this->Internal_CleanupWSByIteratingPointer(@$this->tag_rules[$tag_name]['after_tag'],
+$pos+1, $this->stack);
+$delta = $newpos - ($pos+1);
+$output = $this->Internal_GenerateOutput($newpos);
+$newend = $this->Internal_CleanupWSByIteratingPointer(@$this->tag_rules[$tag_name]['before_endtag'],
+0, $output);
+$output = $this->Internal_CollectTextReverse($output, count($output) - 1, $newend);
+while ($delta-- > 0)
+array_pop($this->stack);
+$this->Internal_ComputeCurrentClass();
+return $output;
+}
+function Internal_ComputeCurrentClass() {
+if (count($this->stack) > 0)
+$this->current_class = $this->stack[count($this->stack)-1][BBCODE_STACK_CLASS];
+else $this->current_class = $this->root_class;
+}
+function Internal_DumpStack($array = false, $raw = false) {
+if (!$raw) $string = "<span style='color: #00C;'>";
+else $string = "";
+if ($array === false)
+$array = $this->stack;
+foreach ($array as $item) {
+switch (@$item[BBCODE_STACK_TOKEN]) {
+case BBCODE_TEXT:
+$string .= "\"" . htmlspecialchars(@$item[BBCODE_STACK_TEXT]) . "\" ";
+break;
+case BBCODE_WS:
+$string .= "WS ";
+break;
+case BBCODE_NL:
+$string .= "NL ";
+break;
+case BBCODE_TAG:
+$string .= "[" . htmlspecialchars(@$item[BBCODE_STACK_TAG]['_name']) . "] ";
+break;
+default:
+$string .= "unknown ";
+break;
+}
+}
+if (!$raw) $string .= "</span>";
+return $string;
+}
+function Internal_CleanupWSByPoppingStack($pattern, &$array) {
+if (strlen($pattern) <= 0) return;
+$oldlen = count($array);
+foreach (str_split($pattern) as $char) {
+switch ($char) {
+case 's':
+while (count($array) > 0 && $array[count($array)-1][BBCODE_STACK_TOKEN] == BBCODE_WS)
+array_pop($array);
+break;
+case 'n':
+if (count($array) > 0 && $array[count($array)-1][BBCODE_STACK_TOKEN] == BBCODE_NL)
+array_pop($array);
+break;
+case 'a':
+while (count($array) > 0
+&& (($token = $array[count($array)-1][BBCODE_STACK_TOKEN]) == BBCODE_WS
+|| $token == BBCODE_NL))
+array_pop($array);
+break;
+}
+}
+if (count($array) != $oldlen) {
+$this->Internal_ComputeCurrentClass();
+}
+}
+function Internal_CleanupWSByEatingInput($pattern) {
+if (strlen($pattern) <= 0) return;
+foreach (str_split($pattern) as $char) {
+switch ($char) {
+case 's':
+$token_type = $this->lexer->NextToken();
+while ($token_type == BBCODE_WS) {
+$token_type = $this->lexer->NextToken();
+}
+$this->lexer->UngetToken();
+break;
+case 'n':
+$token_type = $this->lexer->NextToken();
+if ($token_type != BBCODE_NL)
+$this->lexer->UngetToken();
+break;
+case 'a':
+$token_type = $this->lexer->NextToken();
+while ($token_type == BBCODE_WS || $token_type == BBCODE_NL) {
+$token_type = $this->lexer->NextToken();
+}
+$this->lexer->UngetToken();
+break;
+}
+}
+}
+function Internal_CleanupWSByIteratingPointer($pattern, $pos, $array) {
+if (strlen($pattern) <= 0) return $pos;
+foreach (str_split($pattern) as $char) {
+switch ($char) {
+case 's':
+while ($pos < count($array) && $array[$pos][BBCODE_STACK_TOKEN] == BBCODE_WS)
+$pos++;
+break;
+case 'n':
+if ($pos < count($array) && $array[$pos][BBCODE_STACK_TOKEN] == BBCODE_NL)
+$pos++;
+break;
+case 'a':
+while ($pos < count($array)
+&& (($token = $array[$pos][BBCODE_STACK_TOKEN]) == BBCODE_WS || $token == BBCODE_NL))
+$pos++;
+break;
+}
+}
+return $pos;
+}
+function Internal_LimitText($string, $limit) {
+$chunks = preg_split("/([\\x00-\\x20]+)/", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+$output = "";
+foreach ($chunks as $chunk) {
+if (strlen($output) + strlen($chunk) > $limit)
+break;
+$output .= $chunk;
+}
+$output = rtrim($output);
+return $output;
+}
+function Internal_DoLimit() {
+$this->Internal_CleanupWSByPoppingStack("a", $this->stack);
+if (strlen($this->limit_tail) > 0) {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->limit_tail,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+$this->was_limited = true;
+}
+function DoTag($action, $tag_name, $default_value, $params, $contents) {
+$tag_rule = @$this->tag_rules[$tag_name];
+switch ($action) {
+case BBCODE_CHECK:
+if (isset($tag_rule['allow'])) {
+foreach ($tag_rule['allow'] as $param => $pattern) {
+if ($param == '_content') $value = $contents;
+else if ($param == '_defaultcontent') {
+if (strlen($default_value))
+$value = $default_value;
+else $value = $contents;
+}
+else {
+if (isset($params[$param]))
+$value = $params[$param];
+else $value = @$tag_rule['default'][$param];
+}
+if (!preg_match($pattern, $value)) {
+return false;
+}
+}
+return true;
+}
+switch (@$tag_rule['mode']) {
+default:
+case BBCODE_MODE_SIMPLE:
+$result = true;
+break;
+case BBCODE_MODE_ENHANCED:
+$result = true;
+break;
+case BBCODE_MODE_INTERNAL:
+$result = @call_user_func(Array($this, @$tag_rule['method']), BBCODE_CHECK,
+$tag_name, $default_value, $params, $contents);
+break;
+case BBCODE_MODE_LIBRARY:
+$result = @call_user_func(Array($this->defaults, @$tag_rule['method']), $this, BBCODE_CHECK,
+$tag_name, $default_value, $params, $contents);
+break;
+case BBCODE_MODE_CALLBACK:
+$result = @call_user_func(@$tag_rule['method'], $this, BBCODE_CHECK,
+$tag_name, $default_value, $params, $contents);
+break;
+}
+return $result;
+case BBCODE_OUTPUT:
+if ($this->plain_mode) {
+if (!isset($tag_rule['plain_content']))
+$plain_content = Array('_content');
+else $plain_content = $tag_rule['plain_content'];
+$result = $possible_content = "";
+foreach ($plain_content as $possible_content) {
+if ($possible_content == '_content'
+&& strlen($contents) > 0) {
+$result = $contents;
+break;
+}
+if (isset($params[$possible_content])
+&& strlen($params[$possible_content]) > 0) {
+$result = htmlspecialchars($params[$possible_content]);
+break;
+}
+}
+$start = @$tag_rule['plain_start'];
+$end = @$tag_rule['plain_end'];
+if (isset($tag_rule['plain_link'])) {
+$link = $possible_content = "";
+foreach ($tag_rule['plain_link'] as $possible_content) {
+if ($possible_content == '_content'
+&& strlen($contents) > 0) {
+$link = $this->UnHTMLEncode(strip_tags($contents));
+break;
+}
+if (isset($params[$possible_content])
+&& strlen($params[$possible_content]) > 0) {
+$link = $params[$possible_content];
+break;
+}
+}
+$params = @parse_url($link);
+if (!is_array($params)) $params = Array();
+$params['link'] = $link;
+$params['url'] = $link;
+$start = $this->FillTemplate($start, $params);
+$end = $this->FillTemplate($end, $params);
+}
+return $start . $result . $end;
+}
+switch (@$tag_rule['mode']) {
+default:
+case BBCODE_MODE_SIMPLE:
+$result = @$tag_rule['simple_start'] . $contents . @$tag_rule['simple_end'];
+break;
+case BBCODE_MODE_ENHANCED:
+$result = $this->Internal_DoEnhancedTag($tag_rule, $params, $contents);
+break;
+case BBCODE_MODE_INTERNAL:
+$result = @call_user_func(Array($this, @$tag_rule['method']), BBCODE_OUTPUT,
+$tag_name, $default_value, $params, $contents);
+break;
+case BBCODE_MODE_LIBRARY:
+$result = @call_user_func(Array($this->defaults, @$tag_rule['method']), $this, BBCODE_OUTPUT,
+$tag_name, $default_value, $params, $contents);
+break;
+case BBCODE_MODE_CALLBACK:
+$result = @call_user_func(@$tag_rule['method'], $this, BBCODE_OUTPUT,
+$tag_name, $default_value, $params, $contents);
+break;
+}
+return $result;
+default:
+return false;
+}
+}
+function Internal_DoEnhancedTag($tag_rule, $params, $contents) {
+$params['_content'] = $contents;
+$params['_defaultcontent'] = strlen(@$params['_default']) ? $params['_default'] : $contents;
+return $this->FillTemplate(@$tag_rule['template'], $params, @$tag_rule['default']);
+}
+function Internal_UpdateParamsForMissingEndTag($params) {
+switch ($this->tag_marker) {
+case '[': $tail_marker = ']'; break;
+case '<': $tail_marker = '>'; break;
+case '{': $tail_marker = '}'; break;
+case '(': $tail_marker = ')'; break;
+default: $tail_marker = $this->tag_marker; break;
+}
+$params['_endtag'] = $this->tag_marker . '/' . $params['_name'] . $tail_marker;
+return $params;
+}
+function Internal_ProcessIsolatedTag($tag_name, $tag_params, $tag_rule) {
+if (!$this->DoTag(BBCODE_CHECK, $tag_name, @$tag_params['_default'], $tag_params, "")) {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+return;
+}
+$this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_tag'], $this->stack);
+$output = $this->DoTag(BBCODE_OUTPUT, $tag_name, @$tag_params['_default'], $tag_params, "");
+$this->Internal_CleanupWSByEatingInput(@$tag_rule['after_tag']);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $output,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+function Internal_ProcessVerbatimTag($tag_name, $tag_params, $tag_rule) {
+$state = $this->lexer->SaveState();
+$end_tag = $this->lexer->tagmarker . "/" . $tag_name . $this->lexer->end_tagmarker;
+$start = count($this->stack);
+$this->lexer->verbatim = true;
+while (($token_type = $this->lexer->NextToken()) != BBCODE_EOI) {
+if ($this->lexer->text == $end_tag) {
+$end_tag_params = $this->lexer->tag;
+break;
+}
+if ($this->output_limit > 0
+&& $this->text_length + strlen($this->lexer->text) >= $this->output_limit) {
+$text = $this->Internal_LimitText($this->lexer->text,
+$this->output_limit - $this->text_length);
+if (strlen($text) > 0) {
+$this->text_length += strlen($text);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+$this->Internal_DoLimit();
+break;
+}
+$this->text_length += strlen($this->lexer->text);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => $token_type,
+BBCODE_STACK_TEXT => htmlspecialchars($this->lexer->text),
+BBCODE_STACK_TAG => $this->lexer->tag,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+$this->lexer->verbatim = false;
+if ($token_type == BBCODE_EOI) {
+$this->lexer->RestoreState($state);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+return;
+}
+$newstart = $this->Internal_CleanupWSByIteratingPointer(@$tag_rule['after_tag'], $start, $this->stack);
+$this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_endtag'], $this->stack);
+$this->Internal_CleanupWSByEatingInput(@$tag_rule['after_endtag']);
+$content = $this->Internal_CollectText($this->stack, $newstart);
+array_splice($this->stack, $start);
+$this->Internal_ComputeCurrentClass();
+$this->Internal_CleanupWSByPoppingStack(@$tag_rule['before_tag'], $this->stack);
+$tag_params['_endtag'] = $end_tag_params['_tag'];
+$tag_params['_hasend'] = true;
+$output = $this->DoTag(BBCODE_OUTPUT, $tag_name,
+@$tag_params['_default'], $tag_params, $content);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $output,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+function Internal_ParseStartTagToken() {
+$tag_params = $this->lexer->tag;
+$tag_name = @$tag_params['_name'];
+if (!isset($this->tag_rules[$tag_name])) {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+return;
+}
+$tag_rule = $this->tag_rules[$tag_name];
+$allow_in = is_array($tag_rule['allow_in'])
+? $tag_rule['allow_in'] : Array($this->root_class);
+if (!in_array($this->current_class, $allow_in)) {
+if (!$this->Internal_RewindToClass($allow_in)) {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+return;
+}
+}
+$end_tag = isset($tag_rule['end_tag']) ? $tag_rule['end_tag'] : BBCODE_REQUIRED;
+if ($end_tag == BBCODE_PROHIBIT) {
+$this->Internal_ProcessIsolatedTag($tag_name, $tag_params, $tag_rule);
+return;
+}
+if (!$this->DoTag(BBCODE_CHECK, $tag_name, @$tag_params['_default'], $tag_params, "")) {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+return;
+}
+if (@$tag_rule['content'] == BBCODE_VERBATIM) {
+$this->Internal_ProcessVerbatimTag($tag_name, $tag_params, $tag_rule);
+return;
+}
+if (isset($tag_rule['class']))
+$newclass = $tag_rule['class'];
+else $newclass = $this->root_class;
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => $this->lexer->token,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => $this->lexer->tag,
+BBCODE_STACK_CLASS => ($this->current_class = $newclass),
+);
+if (!isset($this->start_tags[$tag_name]))
+$this->start_tags[$tag_name] = Array(count($this->stack)-1);
+else $this->start_tags[$tag_name][] = count($this->stack)-1;
+}
+function Internal_ParseEndTagToken() {
+$tag_params = $this->lexer->tag;
+$tag_name = @$tag_params['_name'];
+$contents = $this->Internal_FinishTag($tag_name);
+if ($contents === false) {
+if (@$this->lost_start_tags[$tag_name] > 0) {
+$this->lost_start_tags[$tag_name]--;
+}
+else {
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+return;
+}
+$start_tag_node = array_pop($this->stack);
+$start_tag_params = $start_tag_node[BBCODE_STACK_TAG];
+$this->Internal_ComputeCurrentClass();
+$this->Internal_CleanupWSByPoppingStack(@$this->tag_rules[$tag_name]['before_tag'], $this->stack);
+$start_tag_params['_endtag'] = $tag_params['_tag'];
+$start_tag_params['_hasend'] = true;
+$output = $this->DoTag(BBCODE_OUTPUT, $tag_name, @$start_tag_params['_default'],
+$start_tag_params, $contents);
+$this->Internal_CleanupWSByEatingInput(@$this->tag_rules[$tag_name]['after_endtag']);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $output,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+function Parse($string) {
+$this->lexer = new BBCodeLexer($string, $this->tag_marker);
+$this->lexer->debug = $this->debug;
+$old_output_limit = $this->output_limit;
+if ($this->output_limit > 0) {
+if (strlen($string) < $this->output_limit) {
+$this->output_limit = 0;
+}
+else if ($this->limit_precision > 0) {
+$guess_length = $this->lexer->GuessTextLength();
+if ($guess_length < $this->output_limit * ($this->limit_precision + 1.0)) {
+$this->output_limit = 0;
+}
+else {
+}
+}
+}
+$this->stack = Array();
+$this->start_tags = Array();
+$this->lost_start_tags = Array();
+$this->text_length = 0;
+$this->was_limited = false;
+if (strlen($this->pre_trim) > 0)
+$this->Internal_CleanupWSByEatingInput($this->pre_trim);
+$newline = $this->plain_mode ? "\n" : "<br />\n";
+while (true) {
+if (($token_type = $this->lexer->NextToken()) == BBCODE_EOI) {
+break;
+}
+switch ($token_type) {
+case BBCODE_TEXT:
+if ($this->output_limit > 0
+&& $this->text_length + strlen($this->lexer->text) >= $this->output_limit) {
+$text = $this->Internal_LimitText($this->lexer->text,
+$this->output_limit - $this->text_length);
+if (strlen($text) > 0) {
+$this->text_length += strlen($text);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+$this->Internal_DoLimit();
+break 2;
+}
+$this->text_length += strlen($this->lexer->text);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_TEXT,
+BBCODE_STACK_TEXT => $this->FixupOutput($this->lexer->text),
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+break;
+case BBCODE_WS:
+if ($this->output_limit > 0
+&& $this->text_length + strlen($this->lexer->text) >= $this->output_limit) {
+$this->Internal_DoLimit();
+break 2;
+}
+$this->text_length += strlen($this->lexer->text);
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_WS,
+BBCODE_STACK_TEXT => $this->lexer->text,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+break;
+case BBCODE_NL:
+if ($this->ignore_newlines) {
+if ($this->output_limit > 0
+&& $this->text_length + 1 >= $this->output_limit) {
+$this->Internal_DoLimit();
+break 2;
+}
+$this->text_length += 1;
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_WS,
+BBCODE_STACK_TEXT => "\n",
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+}
+else {
+$this->Internal_CleanupWSByPoppingStack("s", $this->stack);
+if ($this->output_limit > 0
+&& $this->text_length + 1 >= $this->output_limit) {
+$this->Internal_DoLimit();
+break 2;
+}
+$this->text_length += 1;
+$this->stack[] = Array(
+BBCODE_STACK_TOKEN => BBCODE_NL,
+BBCODE_STACK_TEXT => $newline,
+BBCODE_STACK_TAG => false,
+BBCODE_STACK_CLASS => $this->current_class,
+);
+$this->Internal_CleanupWSByEatingInput("s");
+}
+break;
+case BBCODE_TAG:
+$this->Internal_ParseStartTagToken();
+break;
+case BBCODE_ENDTAG:
+$this->Internal_ParseEndTagToken();
+break;
+default:
+break;
+}
+}
+if (strlen($this->post_trim) > 0)
+$this->Internal_CleanupWSByPoppingStack($this->post_trim, $this->stack);
+$result = $this->Internal_GenerateOutput(0);
+$result = $this->Internal_CollectTextReverse($result, count($result) - 1);
+$this->output_limit = $old_output_limit;
+if ($this->plain_mode) {
+$result = preg_replace("/[\\x00-\\x09\\x0B-\\x20]+/", " ", $result);
+$result = preg_replace("/(?:[\\x20]*\\n){2,}[\\x20]*/", "\n\n", $result);
+$result = trim($result);
+}
+return $result;
+}
+}
Index: /tags/2.6.6/src/includes/admin/replies.php
===================================================================
--- /tags/2.6.6/src/includes/admin/replies.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/replies.php	(revision 7162)
@@ -0,0 +1,1055 @@
+<?php
+
+/**
+ * bbPress Replies Admin Class
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Replies_Admin' ) ) :
+/**
+ * Loads bbPress replies admin area
+ *
+ * @package bbPress
+ * @subpackage Administration
+ * @since 2.0.0 bbPress (r2464)
+ */
+class BBP_Replies_Admin {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * @var The post type of this admin component
+	 */
+	private $post_type = '';
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main bbPress admin loader
+	 *
+	 * @since 2.0.0 bbPress (r2515)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the admin hooks, actions and filters
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 * @since 2.6.0 bbPress (r6101) Added bulk actions
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Messages
+		add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
+
+		// Reply bulk actions, added in WordPress 4.7, see #WP16031
+		if ( bbp_get_major_wp_version() >= 4.7 ) {
+			add_filter( 'bulk_actions-edit-reply',        array( $this, 'bulk_actions' ) );
+			add_filter( 'handle_bulk_actions-edit-reply', array( $this, 'handle_bulk_actions' ), 10, 3 );
+			add_filter( 'bulk_post_updated_messages',     array( $this, 'bulk_post_updated_messages' ), 10, 2 );
+		}
+
+		// Reply column headers.
+		add_filter( 'manage_' . $this->post_type . '_posts_columns',  array( $this, 'column_headers' ) );
+
+		// Reply columns (in post row)
+		add_action( 'manage_' . $this->post_type . '_posts_custom_column',  array( $this, 'column_data' ), 10, 2 );
+		add_filter( 'post_row_actions',                                     array( $this, 'row_actions' ), 10, 2 );
+
+		// Reply meta-box actions
+		add_action( 'add_meta_boxes', array( $this, 'attributes_metabox' ) );
+		add_action( 'add_meta_boxes', array( $this, 'author_metabox'     ) );
+		add_action( 'add_meta_boxes', array( $this, 'comments_metabox'   ) );
+		add_action( 'save_post',      array( $this, 'save_meta_boxes'    ) );
+
+		// Check if there are any bbp_toggle_reply_* requests on admin_init, also have a message displayed
+		add_action( 'load-edit.php', array( $this, 'toggle_reply'        ) );
+		add_action( 'load-edit.php', array( $this, 'toggle_reply_notice' ) );
+
+		// Add ability to filter topics and replies per forum
+		add_filter( 'restrict_manage_posts', array( $this, 'filter_dropdown'  ) );
+		add_filter( 'bbp_request',           array( $this, 'filter_post_rows' ) );
+
+		// Empty spam
+		add_filter( 'manage_posts_extra_tablenav', array( $this, 'filter_empty_spam' ) );
+
+		// Contextual Help
+		add_action( 'load-edit.php',     array( $this, 'edit_help' ) );
+		add_action( 'load-post.php',     array( $this, 'new_help'  ) );
+		add_action( 'load-post-new.php', array( $this, 'new_help'  ) );
+	}
+
+	/**
+	 * Admin globals
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+		$this->post_type = bbp_get_reply_post_type();
+	}
+
+	/** Contextual Help *******************************************************/
+
+	/**
+	 * Contextual help for bbPress reply edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function edit_help() {
+
+		// Overview
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'overview',
+			'title'		=> __( 'Overview', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'This screen provides access to all of your replies. You can customize the display of this screen to suit your workflow.', 'bbpress' ) . '</p>'
+		) );
+
+		// Screen Content
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'screen-content',
+			'title'		=> __( 'Screen Content', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can customize the display of this screen&#8217;s contents in a number of ways:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( 'You can hide/display columns based on your needs and decide how many replies to list per screen using the Screen Options tab.',                                                                         'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can filter the list of replies by reply status using the text links in the upper left to show All, Published, Draft, Pending, Trashed, or Spam replies. The default view is to show all replies.',  'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can view replies in a simple title list or with an excerpt. Choose the view you prefer by clicking on the icons at the top of the list on the right.',                                              'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can refine the list to show only replies in a specific forum or from a specific month by using the dropdown menus above the replies list. Click the Filter button after making your selection.', 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Available Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'action-links',
+			'title'		=> __( 'Available Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'Hovering over a row in the replies list will display action links that allow you to manage your reply. You can perform the following actions:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Edit</strong> takes you to the editing screen for that reply. You can also reach that screen by clicking on the reply title.',                  'bbpress' ) . '</li>' .
+					//'<li>' . __( '<strong>Quick Edit</strong> provides inline access to the metadata of your reply, allowing you to update reply details without leaving this screen.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Trash</strong> removes your reply from this list and places it in the trash, from which you can permanently delete it.',                        'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Spam</strong> removes your reply from this list and places it in the spam queue, from which you can permanently delete it.',                    'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>View</strong> will take you to your live site to view the reply.',                                                                              'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Approve</strong> will change the status from pending to publish.',                                                                              'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Bulk Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'bulk-actions',
+			'title'		=> __( 'Bulk Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can also edit, spam, or move multiple replies to the trash at once. Select the replies you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.',           'bbpress' ) . '</p>' .
+				'<p>' . __( 'When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected replies at once. To remove a reply from the grouping, just click the x next to its name in the Bulk Edit area that appears.', 'bbpress' ) . '</p>'
+		) );
+
+		// Help Sidebar
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Contextual help for bbPress reply edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function new_help() {
+
+		$customize_display = '<p>' . __( 'The title field and the big reply editing Area are fixed in place, but you can reposition all the other boxes using drag and drop, and can minimize or expand them by clicking the title bar of each box. Use the Screen Options tab to unhide more boxes (Excerpt, Send Trackbacks, Custom Fields, Discussion, Slug, Author) or to choose a 1- or 2-column layout for this screen.', 'bbpress' ) . '</p>';
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'customize-display',
+			'title'   => __( 'Customizing This Display', 'bbpress' ),
+			'content' => $customize_display,
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'title-reply-editor',
+			'title'   => __( 'Title and Reply Editor', 'bbpress' ),
+			'content' =>
+				'<p>' . __( '<strong>Title</strong> - Enter a title for your reply. After you enter a title, you&#8217;ll see the permalink below, which you can edit.', 'bbpress' ) . '</p>' .
+				'<p>' . __( '<strong>Reply Editor</strong> - Enter the text for your reply. There are two modes of editing: Visual and HTML. Choose the mode by clicking on the appropriate tab. Visual mode gives you a WYSIWYG editor. Click the last icon in the row to get a second row of controls. The HTML mode allows you to enter raw HTML along with your reply text. You can insert media files by clicking the icons above the reply editor and following the directions. You can go to the distraction-free writing screen via the Fullscreen icon in Visual mode (second to last in the top row) or the Fullscreen button in HTML mode (last in the row). Once there, you can make buttons visible by hovering over the top area. Exit Fullscreen back to the regular reply editor.', 'bbpress' ) . '</p>'
+		) );
+
+		$publish_box = '<p>' . __( '<strong>Publish</strong> - You can set the terms of publishing your reply in the Publish box. For Status, Visibility, and Publish (immediately), click on the Edit link to reveal more options. Visibility includes options for password-protecting a reply or making it stay at the top of your blog indefinitely (sticky). Publish (immediately) allows you to set a future or past date and time, so you can schedule a reply to be published in the future or backdate a reply.', 'bbpress' ) . '</p>';
+
+		if ( current_theme_supports( 'reply-thumbnails' ) && post_type_supports( bbp_get_reply_post_type(), 'thumbnail' ) ) {
+			$publish_box .= '<p>' . __( '<strong>Featured Image</strong> - This allows you to associate an image with your reply without inserting it. This is usually useful only if your theme makes use of the featured image as a reply thumbnail on the home page, a custom header, etc.', 'bbpress' ) . '</p>';
+		}
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'reply-attributes',
+			'title'   => __( 'Reply Attributes', 'bbpress' ),
+			'content' =>
+				'<p>' . __( 'Select the attributes that your reply should have:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Forum</strong> dropdown determines the parent forum that the reply belongs to. Select the forum, or leave the default (Use Forum of Topic) to post the reply in forum of the topic.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Topic</strong> determines the parent topic that the reply belongs to.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Reply To</strong> determines the threading of the reply.', 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'publish-box',
+			'title'   => __( 'Publish Box', 'bbpress' ),
+			'content' => $publish_box,
+		) );
+
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Add spam/unspam bulk actions to the bulk action dropdown.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param array $actions The list of bulk actions.
+	 * @return array The filtered list of bulk actions.
+	 */
+	public function bulk_actions( $actions ) {
+
+		if ( current_user_can( 'moderate' ) ) {
+			if ( bbp_get_spam_status_id() === get_query_var( 'post_status' ) ) {
+				$actions['unspam'] = esc_html__( 'Unspam', 'bbpress' );
+			} else {
+				$actions['spam'] = esc_html__( 'Spam', 'bbpress' );
+			}
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Add custom bulk action updated messages for replies.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param array $bulk_messages Arrays of messages, each keyed by the corresponding post type.
+	 * @param array $bulk_counts   Array of item counts for each message, used to build internationalized strings.
+	 */
+	public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
+		$bulk_messages['reply']['updated'] = _n( '%s reply updated.', '%s replies updated.', $bulk_counts['updated'], 'bbpress');
+		$bulk_messages['reply']['locked']  = ( 1 === $bulk_counts['locked'] )
+			? __( '1 reply not updated, somebody is editing it.', 'bbpress' )
+			: _n( '%s reply not updated, somebody is editing it.', '%s replies not updated, somebody is editing them.', $bulk_counts['locked'], 'bbpress' );
+
+		return $bulk_messages;
+	}
+
+	/**
+	 * Handle spam/unspam bulk actions.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param string $sendback The sendback URL.
+	 * @param string $doaction The action to be taken.
+	 * @param array  $post_ids The post IDS to take the action on.
+	 * @return string The sendback URL.
+	 */
+	public function handle_bulk_actions( $sendback, $doaction, $post_ids ) {
+
+		$sendback = remove_query_arg( array( 'spam', 'unspam' ), $sendback );
+		$updated = $locked = 0;
+
+		if ( 'spam' === $doaction ) {
+
+			foreach ( (array) $post_ids as $post_id ) {
+				if ( ! current_user_can( 'moderate', $post_id ) ) {
+					wp_die( esc_html__( 'Sorry, you are not allowed to spam this item.', 'bbpress' ) );
+				}
+
+				if ( wp_check_post_lock( $post_id ) ) {
+					$locked++;
+					continue;
+				}
+
+				if ( ! bbp_spam_reply( $post_id ) ) {
+					wp_die( esc_html__( 'Error in spamming reply.', 'bbpress' ) );
+				}
+
+				$updated++;
+			}
+
+			$sendback = add_query_arg( array(
+				'updated' => $updated,
+				'ids'     => implode( ',', $post_ids ),
+				'locked'  => $locked
+			), $sendback );
+
+		} elseif ( 'unspam' === $doaction ) {
+
+			foreach ( (array) $post_ids as $post_id ) {
+				if ( ! current_user_can( 'moderate', $post_id ) ) {
+					wp_die( esc_html__( 'Sorry, you are not allowed to unspam this reply.', 'bbpress' ) );
+				}
+
+				if ( wp_check_post_lock( $post_id ) ) {
+					$locked++;
+					continue;
+				}
+
+				if ( ! bbp_unspam_reply( $post_id ) ) {
+					wp_die( esc_html__( 'Error in unspamming reply.', 'bbpress' ) );
+				}
+
+				$updated++;
+			}
+
+			$sendback = add_query_arg( array(
+				'updated' => $updated,
+				'ids'     => implode( ',', $post_ids ),
+				'locked'  => $locked
+			), $sendback );
+		}
+
+		return $sendback;
+	}
+
+	/**
+	 * Add the reply attributes meta-box
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 */
+	public function attributes_metabox() {
+		add_meta_box(
+			'bbp_reply_attributes',
+			esc_html__( 'Reply Attributes', 'bbpress' ),
+			'bbp_reply_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the author info meta-box
+	 *
+	 * Allows editing of information about an author
+	 *
+	 * @since 2.0.0 bbPress (r2828)
+	 */
+	public function author_metabox() {
+
+		// Bail if post_type is not a reply
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_author_metabox',
+			esc_html__( 'Author Information', 'bbpress' ),
+			'bbp_author_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Remove comments & discussion meta-boxes if comments are not supported
+	 *
+	 * @since 2.6.0 bbPress (r6186)
+	 */
+	public function comments_metabox() {
+		if ( ! post_type_supports( $this->post_type, 'comments' ) ) {
+			remove_meta_box( 'commentstatusdiv', $this->post_type, 'normal' );
+			remove_meta_box( 'commentsdiv',      $this->post_type, 'normal' );
+		}
+	}
+
+	/**
+	 * Pass the reply attributes for processing
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @param int $reply_id Reply id
+	 * @return int Parent id
+	 */
+	public function save_meta_boxes( $reply_id ) {
+
+		// Bail if doing an autosave
+		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
+			return $reply_id;
+		}
+
+		// Bail if not a post request
+		if ( ! bbp_is_post_request() ) {
+			return $reply_id;
+		}
+
+		// Check action exists
+		if ( empty( $_POST['action'] ) ) {
+			return $reply_id;
+		}
+
+		// Nonce check
+		if ( empty( $_POST['bbp_reply_metabox'] ) || ! wp_verify_nonce( $_POST['bbp_reply_metabox'], 'bbp_reply_metabox_save' ) ) {
+			return $reply_id;
+		}
+
+		// Bail if current user cannot edit this reply
+		if ( ! current_user_can( 'edit_reply', $reply_id ) ) {
+			return $reply_id;
+		}
+
+		// Get the reply meta post values
+		$topic_id = ! empty( $_POST['parent_id']    ) ? (int) $_POST['parent_id']    : 0;
+		$forum_id = ! empty( $_POST['bbp_forum_id'] ) ? (int) $_POST['bbp_forum_id'] : bbp_get_topic_forum_id( $topic_id );
+		$reply_to = ! empty( $_POST['bbp_reply_to'] ) ? (int) $_POST['bbp_reply_to'] : 0;
+
+		// Get reply author data
+		$anonymous_data = bbp_filter_anonymous_post_data();
+		$author_id      = bbp_get_reply_author_id( $reply_id );
+		$is_edit        = ( isset( $_POST['hidden_post_status'] ) && ( $_POST['hidden_post_status'] !== 'draft' ) );
+
+		// Formally update the reply
+		bbp_update_reply( $reply_id, $topic_id, $forum_id, $anonymous_data, $author_id, $is_edit, $reply_to );
+
+		// Allow other fun things to happen
+		do_action( 'bbp_reply_attributes_metabox_save', $reply_id, $topic_id, $forum_id, $reply_to );
+		do_action( 'bbp_author_metabox_save',           $reply_id, $anonymous_data                 );
+
+		return $reply_id;
+	}
+
+	/**
+	 * Toggle reply
+	 *
+	 * Handles the admin-side spamming/unspamming of replies
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 */
+	public function toggle_reply() {
+
+		// Bail if not a topic toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['action'] ) || empty( $_GET['reply_id'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed action
+		$action = sanitize_key( $_GET['action'] );
+		if ( empty( $action ) || ! in_array( $action, $this->get_allowed_action_toggles(), true ) ) {
+			return;
+		}
+
+		// Get reply and die if empty
+		$reply_id = bbp_get_reply_id( $_GET['reply_id'] );
+		if ( ! bbp_get_reply( $reply_id ) ) {
+			wp_die( esc_html__( 'The reply was not found.', 'bbpress' ) );
+		}
+
+		// What is the user doing here?
+		if ( ! current_user_can( 'moderate', $reply_id ) ) {
+			wp_die( esc_html__( 'You do not have permission to do that.', 'bbpress' ) );
+		}
+
+		// Defaults
+		$post_data = array( 'ID' => $reply_id );
+		$message   = '';
+		$success   = false;
+
+		switch ( $action ) {
+			case 'bbp_toggle_reply_approve' :
+				check_admin_referer( 'approve-reply_' . $reply_id );
+
+				$is_approve = bbp_is_reply_public( $reply_id );
+				$message    = ( true === $is_approve )
+					? 'unpproved'
+					: 'approved';
+				$success    = ( true === $is_approve )
+					? bbp_unapprove_reply( $reply_id )
+					: bbp_approve_reply( $reply_id );
+
+				break;
+
+			case 'bbp_toggle_reply_spam' :
+				check_admin_referer( 'spam-reply_' . $reply_id );
+
+				$is_spam = bbp_is_reply_spam( $reply_id );
+				$message = ( true === $is_spam )
+					? 'unspammed'
+					: 'spammed';
+				$success = ( true === $is_spam )
+					? bbp_unspam_reply( $reply_id )
+					: bbp_spam_reply( $reply_id );
+
+				break;
+		}
+
+		// Setup the message
+		$retval = array(
+			'bbp_reply_toggle_notice' => $message,
+			'reply_id'                => $reply_id
+		);
+
+		// Prepare for failure
+		if ( ( false === $success ) || is_wp_error( $success ) ) {
+			$retval['failed'] = '1';
+		}
+
+		// Filter all message args
+		$retval = apply_filters( 'bbp_toggle_reply_action_admin', $retval, $reply_id, $action );
+
+		// Do additional reply toggle actions (admin side)
+		do_action( 'bbp_toggle_reply_admin', $success, $post_data, $action, $retval );
+
+		// Redirect back to the reply
+		$redirect = add_query_arg( $retval, remove_query_arg( array( 'action', 'reply_id' ) ) );
+		bbp_redirect( $redirect );
+	}
+
+	/**
+	 * Toggle reply notices
+	 *
+	 * Display the success/error notices from
+	 * {@link BBP_Admin::toggle_reply()}
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 */
+	public function toggle_reply_notice() {
+
+		// Bail if missing reply toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['reply_id'] ) || empty( $_GET['bbp_reply_toggle_notice'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed notice
+		$notice = sanitize_key( $_GET['bbp_reply_toggle_notice'] );
+		if ( empty( $notice ) || ! in_array( $notice, $this->get_allowed_notice_toggles(), true ) ) {
+			return;
+		}
+
+		// Bail if no reply_id or notice
+		$reply_id = bbp_get_reply_id( $_GET['reply_id'] );
+		if ( empty( $reply_id ) ) {
+			return;
+		}
+
+		// Bail if reply is missing
+		if ( ! bbp_get_reply( $reply_id ) ) {
+			return;
+		}
+
+		// Use the title in the responses
+		$reply_title = bbp_get_reply_title( $reply_id );
+		$is_failure  = ! empty( $_GET['failed'] );
+		$message     = '';
+
+		switch ( $notice ) {
+			case 'spammed' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem marking the reply "%1$s" as spam.', 'bbpress' ), $reply_title )
+					: sprintf( esc_html__( 'Reply "%1$s" successfully marked as spam.',             'bbpress' ), $reply_title );
+				break;
+
+			case 'unspammed' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem unmarking the reply "%1$s" as spam.', 'bbpress' ), $reply_title )
+					: sprintf( esc_html__( 'Reply "%1$s" successfully unmarked as spam.',             'bbpress' ), $reply_title );
+				break;
+
+			case 'approved' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem approving the reply "%1$s".', 'bbpress' ), $reply_title )
+					: sprintf( esc_html__( 'Reply "%1$s" successfully approved.',             'bbpress' ), $reply_title );
+				break;
+
+			case 'unapproved' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem unapproving the reply "%1$s".', 'bbpress' ), $reply_title )
+					: sprintf( esc_html__( 'Reply "%1$s" successfully unapproved.',             'bbpress' ), $reply_title );
+				break;
+		}
+
+		// Do additional reply toggle notice filters (admin side)
+		$message = apply_filters( 'bbp_toggle_reply_notice_admin', $message, $reply_id, $notice, $is_failure );
+		$class   = ( $is_failure === true )
+			? 'error'
+			: 'updated';
+
+		// Add the notice
+		bbp_admin()->add_notice( $message, $class, true );
+	}
+
+	/**
+	 * Returns an array of keys used to sort row actions
+	 *
+	 * @since 2.6.0 bbPress (r6771)
+	 *
+	 * @return array
+	 */
+	private function get_row_action_sort_order() {
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_reply_row_action_sort_order', array(
+			'edit',
+			'approved',
+			'unapproved',
+			'spam',
+			'unspam',
+			'trash',
+			'untrash',
+			'delete',
+			'view'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_notice_toggles() {
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_replies_allowed_notice_toggles', array(
+			'spammed',
+			'unspammed',
+			'approved',
+			'unapproved'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_action_toggles() {
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_replies_allowed_action_toggles', array(
+			'bbp_toggle_reply_spam',
+			'bbp_toggle_reply_approve'
+		) );
+	}
+
+	/**
+	 * Manage the column headers for the replies page
+	 *
+	 * @since 2.0.0 bbPress (r2577)
+	 *
+	 * @param array $columns The columns
+	 *
+	 * @return array $columns bbPress reply columns
+	 */
+	public function column_headers( $columns ) {
+		$columns = array(
+			'cb'                => '<input type="checkbox" />',
+			'title'             => esc_html__( 'Title',   'bbpress' ),
+			'bbp_reply_forum'   => esc_html__( 'Forum',   'bbpress' ),
+			'bbp_reply_topic'   => esc_html__( 'Topic',   'bbpress' ),
+			'bbp_reply_author'  => esc_html__( 'Author',  'bbpress' ),
+			'bbp_reply_created' => esc_html__( 'Created', 'bbpress' ),
+		);
+
+		// Filter & return
+		return apply_filters( 'bbp_admin_replies_column_headers', $columns );
+	}
+
+	/**
+	 * Print extra columns for the replies page
+	 *
+	 * @since 2.0.0 bbPress (r2577)
+	 *
+	 * @param string $column Column
+	 * @param int $reply_id reply id
+	 */
+	public function column_data( $column, $reply_id ) {
+
+		// Get topic ID
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Populate Column Data
+		switch ( $column ) {
+
+			// Topic
+			case 'bbp_reply_topic' :
+
+				// Get title
+				$topic_title = ! empty( $topic_id )
+					? bbp_get_topic_title( $topic_id )
+					: '';
+
+				// Output topic name
+				if ( ! empty( $topic_title ) ) {
+					echo $topic_title;
+
+				// Output dash
+				} else {
+					?>
+					<span aria-hidden="true">&mdash;</span>
+					<span class="screen-reader-text"><?php esc_html_e( 'No topic', 'bbpress' ); ?></span>
+					<?php
+				}
+
+				break;
+
+			// Forum
+			case 'bbp_reply_forum' :
+
+				// Get Forum ID's
+				$reply_forum_id = bbp_get_reply_forum_id( $reply_id );
+				$topic_forum_id = bbp_get_topic_forum_id( $topic_id );
+
+				// Forum Title
+				$forum_title = ! empty( $reply_forum_id )
+					? bbp_get_forum_title( $reply_forum_id )
+					: '';
+
+				// Alert capable users of reply forum mismatch
+				if ( $reply_forum_id !== $topic_forum_id ) {
+					if ( current_user_can( 'edit_others_replies' ) || current_user_can( 'moderate', $reply_id ) ) {
+						$forum_title .= '<div class="attention">' . esc_html__( '(Mismatch)', 'bbpress' ) . '</div>';
+					}
+				}
+
+				// Output forum name
+				if ( ! empty( $forum_title ) ) {
+					echo $forum_title;
+
+				// Reply has no forum
+				} else {
+					?>
+					<span aria-hidden="true">&mdash;</span>
+					<span class="screen-reader-text"><?php esc_html_e( 'No forum', 'bbpress' ); ?></span>
+					<?php
+				}
+
+				break;
+
+			// Author
+			case 'bbp_reply_author' :
+				bbp_reply_author_display_name( $reply_id );
+				break;
+
+			// Freshness
+			case 'bbp_reply_created':
+
+				// Output last activity time and date
+				printf( '%1$s <br /> %2$s',
+					get_the_date(),
+					esc_attr( get_the_time() )
+				);
+
+				break;
+
+			// Do action for anything else
+			default :
+				do_action( 'bbp_admin_replies_column_data', $column, $reply_id );
+				break;
+		}
+	}
+
+	/**
+	 * Reply Row actions
+	 *
+	 * Remove the quick-edit action link under the reply title and add the
+	 * content and spam link
+	 *
+	 * @since 2.0.0 bbPress (r2577)
+	 *
+	 * @param array  $actions Actions
+	 * @param object $reply   Reply object
+	 *
+	 * @return array $actions Actions
+	 */
+	public function row_actions( $actions = array(), $reply = false ) {
+
+		// Disable quick edit (too much to do here)
+		unset( $actions['inline hide-if-no-js'] );
+
+		// View link
+		$view_link = bbp_get_reply_url( $reply->ID );
+
+		// Maybe add view=all
+		if ( ! in_array( $reply->post_status, array( bbp_get_closed_status_id(), bbp_get_public_status_id() ), true ) ) {
+			$view_link = bbp_add_view_all( $view_link, true );
+		}
+
+		// Reply view links to topic
+		$actions['view'] = '<a href="' . esc_url( $view_link ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'bbpress' ), bbp_get_reply_title( $reply->ID ) ) ) . '" rel="permalink">' . esc_html__( 'View', 'bbpress' ) . '</a>';
+
+		// User cannot view replies in trash
+		if ( ( bbp_get_trash_status_id() === $reply->post_status ) && ! current_user_can( 'view_trash' ) ) {
+			unset( $actions['view'] );
+		}
+
+		// Only show the actions if the user is capable of viewing them
+		if ( current_user_can( 'moderate', $reply->ID ) ) {
+
+			// Show the 'approve' link on non-published posts only and 'unapprove' on published posts only
+			$approve_uri = wp_nonce_url( add_query_arg( array( 'reply_id' => $reply->ID, 'action' => 'bbp_toggle_reply_approve' ), remove_query_arg( array( 'bbp_reply_toggle_notice', 'reply_id', 'failed', 'super' ) ) ), 'approve-reply_' . $reply->ID );
+			if ( bbp_is_reply_public( $reply->ID ) ) {
+				$actions['unapproved'] = '<a href="' . esc_url( $approve_uri ) . '" title="' . esc_attr__( 'Unapprove this reply', 'bbpress' ) . '">' . _x( 'Unapprove', 'Unapprove reply', 'bbpress' ) . '</a>';
+			} else {
+				$actions['approved']   = '<a href="' . esc_url( $approve_uri ) . '" title="' . esc_attr__( 'Approve this reply',   'bbpress' ) . '">' . _x( 'Approve',   'Approve reply',   'bbpress' ) . '</a>';
+			}
+
+			// Show the 'spam' link on published and pending replies and 'not spam' on spammed replies
+			if ( in_array( $reply->post_status, array( bbp_get_public_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id(), bbp_get_spam_status_id() ), true ) ) {
+				$spam_uri  = wp_nonce_url( add_query_arg( array( 'reply_id' => $reply->ID, 'action' => 'bbp_toggle_reply_spam' ), remove_query_arg( array( 'bbp_reply_toggle_notice', 'reply_id', 'failed', 'super' ) ) ), 'spam-reply_'  . $reply->ID );
+				if ( ! bbp_is_reply_spam( $reply->ID ) ) {
+					$actions['spam'] = '<a href="' . esc_url( $spam_uri ) . '" title="' . esc_attr__( 'Mark this reply as spam',    'bbpress' ) . '">' . esc_html__( 'Spam',     'bbpress' ) . '</a>';
+				} else {
+					$actions['unspam'] = '<a href="' . esc_url( $spam_uri ) . '" title="' . esc_attr__( 'Mark the reply as not spam', 'bbpress' ) . '">' . esc_html__( 'Not Spam', 'bbpress' ) . '</a>';
+				}
+			}
+		}
+
+		// Trash
+		if ( current_user_can( 'delete_reply', $reply->ID ) ) {
+			$trash_days = bbp_get_trash_days( bbp_get_reply_post_type() );
+
+			if ( bbp_get_trash_status_id() === $reply->post_status ) {
+				$post_type_object   = get_post_type_object( bbp_get_reply_post_type() );
+				$actions['untrash'] = "<a title='" . esc_attr__( 'Restore this item from the Trash', 'bbpress' ) . "' href='" . esc_url( wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $reply->ID ) ), 'untrash-post_' . $reply->ID ) ) . "'>" . esc_html__( 'Restore', 'bbpress' ) . "</a>";
+			} elseif ( ! empty( $trash_days ) ) {
+				$actions['trash'] = "<a class='submitdelete' title='" . esc_attr__( 'Move this item to the Trash', 'bbpress' ) . "' href='" . esc_url( get_delete_post_link( $reply->ID ) ) . "'>" . esc_html__( 'Trash', 'bbpress' ) . "</a>";
+			}
+
+			if ( ( bbp_get_trash_status_id() === $reply->post_status ) || empty( $trash_days ) ) {
+				$actions['delete'] = "<a class='submitdelete' title='" . esc_attr__( 'Delete this item permanently', 'bbpress' ) . "' href='" . esc_url( get_delete_post_link( $reply->ID, '', true ) ) . "'>" . esc_html__( 'Delete Permanently', 'bbpress' ) . "</a>";
+			}
+		}
+
+		// Sort & return
+		return $this->sort_row_actions( $actions );
+	}
+
+	/**
+	 * Sort row actions by key
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param array $actions
+	 *
+	 * @return array
+	 */
+	private function sort_row_actions( $actions = array() ) {
+
+		// Return value
+		$retval = array();
+
+		// Known row actions, in sort order
+		$known_actions = $this->get_row_action_sort_order();
+
+		// Sort known actions, and keep any unknown ones
+		foreach ( $known_actions as $key ) {
+			if ( isset( $actions[ $key ] ) ) {
+				$retval[ $key ] = $actions[ $key ];
+				unset( $actions[ $key ] );
+			}
+		}
+
+		// Combine & return
+		return $retval + $actions;
+	}
+
+	/**
+	 * Add forum dropdown to topic and reply list table filters
+	 *
+	 * @since 2.0.0 bbPress (r2991)
+	 *
+	 * @return bool False. If post type is not topic or reply
+	 */
+	public function filter_dropdown() {
+
+		// Get which forum is selected
+		$selected = ! empty( $_GET['bbp_forum_id'] )
+			? (int) $_GET['bbp_forum_id']
+			: 0;
+
+		// Show the forums dropdown
+		bbp_dropdown( array(
+			'selected'  => $selected,
+			'show_none' => esc_html__( 'In all forums', 'bbpress' )
+		) );
+	}
+
+	/**
+	 * Add "Empty Spam" button for moderators
+	 *
+	 * @since 2.6.0 bbPress (r6791)
+	 */
+	public function filter_empty_spam() {
+
+		// Bail if not viewing spam
+		if ( empty( $_GET['post_status'] ) || ( bbp_get_spam_status_id() !== $_GET['post_status'] ) && current_user_can( 'moderate' ) ) {
+			return;
+		}
+
+		?>
+
+		<div class="alignleft actions"><?php
+
+			// Output the nonce & button
+			wp_nonce_field( 'bulk-destroy', '_destroy_nonce' );
+			submit_button(
+				esc_attr__( 'Empty Spam', 'bbpress' ),
+				'button-secondary apply',
+				'delete_all',
+				false
+			);
+
+		?></div><?php
+	}
+
+	/**
+	 * Adjust the request query and include the forum id
+	 *
+	 * @since 2.0.0 bbPress (r2991)
+	 *
+	 * @param array $query_vars Query variables from {@link WP_Query}
+	 * @return array Processed Query Vars
+	 */
+	public function filter_post_rows( $query_vars ) {
+
+		// Add post_parent query_var if one is present
+		if ( ! empty( $_GET['bbp_forum_id'] ) ) {
+			$query_vars['meta_key']   = '_bbp_forum_id';
+			$query_vars['meta_type']  = 'NUMERIC';
+			$query_vars['meta_value'] = $_GET['bbp_forum_id'];
+		}
+
+		// Return manipulated query_vars
+		return $query_vars;
+	}
+
+	/**
+	 * Custom user feedback messages for reply post type
+	 *
+	 * @since 2.0.0 bbPress (r3080)
+	 *
+	 * @global int $post_ID
+	 *
+	 * @param array $messages
+	 *
+	 * @return array
+	 */
+	public function updated_messages( $messages ) {
+		global $post_ID;
+
+		// URL for the current topic
+		$topic_url = bbp_get_topic_permalink( bbp_get_reply_topic_id( $post_ID ) );
+
+		// Current reply's post_date
+		$post_date = bbp_get_global_post_field( 'post_date', 'raw' );
+
+		// Messages array
+		$messages[ $this->post_type ] = array(
+			0 =>  '', // Left empty on purpose
+
+			// Updated
+			1 =>  sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Reply updated.', 'bbpress' ),
+				$topic_url,
+				esc_html__( 'View topic', 'bbpress' )
+			),
+
+			// Custom field updated
+			2 => esc_html__( 'Custom field updated.', 'bbpress' ),
+
+			// Custom field deleted
+			3 => esc_html__( 'Custom field deleted.', 'bbpress' ),
+
+			// Reply updated
+			4 => esc_html__( 'Reply updated.', 'bbpress' ),
+
+			// Restored from revision
+			// translators: %s: date and time of the revision
+			5 => isset( $_GET['revision'] )
+					? sprintf( esc_html__( 'Reply restored to revision from %s', 'bbpress' ), wp_post_revision_title( (int) $_GET['revision'], false ) )
+					: false,
+
+			// Reply created
+			6 => sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Reply created.', 'bbpress' ),
+				$topic_url,
+				esc_html__( 'View topic', 'bbpress' )
+			),
+
+			// Reply saved
+			7 => esc_html__( 'Reply saved.', 'bbpress' ),
+
+			// Reply submitted
+			8 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Reply submitted.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $topic_url ) ),
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+
+			// Reply scheduled
+			9 => sprintf(
+				'%1$s <a target="_blank" href="%2$s">%3$s</a>',
+				sprintf(
+					esc_html__( 'Reply scheduled for: %s.', 'bbpress' ),
+					// translators: Publish box date format, see http://php.net/date
+					'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'bbpress' ), strtotime( $post_date ) ) . '</strong>'
+				),
+				$topic_url,
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+
+			// Reply draft updated
+			10 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Reply draft updated.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $topic_url ) ),
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+		);
+
+		return $messages;
+	}
+}
+endif; // class_exists check
+
+/**
+ * Setup bbPress Replies Admin
+ *
+ * This is currently here to make hooking and unhooking of the admin UI easy.
+ * It could use dependency injection in the future, but for now this is easier.
+ *
+ * @since 2.0.0 bbPress (r2596)
+ *
+ * @param WP_Screen $current_screen Current screen object
+ */
+function bbp_admin_replies( $current_screen ) {
+
+	// Bail if not a forum screen
+	if ( empty( $current_screen->post_type ) || ( bbp_get_reply_post_type() !== $current_screen->post_type ) ) {
+		return;
+	}
+
+	// Init the replies admin
+	bbp_admin()->replies = new BBP_Replies_Admin();
+}
Index: /tags/2.6.6/src/includes/admin/settings.php
===================================================================
--- /tags/2.6.6/src/includes/admin/settings.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/settings.php	(revision 7162)
@@ -0,0 +1,2102 @@
+<?php
+
+/**
+ * bbPress Admin Settings
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Sections ******************************************************************/
+
+/**
+ * Get the Forums settings sections.
+ *
+ * @since 2.1.0 bbPress (r4001)
+ *
+ * @return array
+ */
+function bbp_admin_get_settings_sections() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_admin_get_settings_sections', array(
+
+		// Settings
+		'bbp_settings_users' => array(
+			'title'    => esc_html__( 'Forum User Settings', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_user_section',
+			'page'     => 'discussion'
+		),
+		'bbp_settings_features' => array(
+			'title'    => esc_html__( 'Forum Features', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_features_section',
+			'page'     => 'discussion'
+		),
+		'bbp_settings_theme_compat' => array(
+			'title'    => esc_html__( 'Forum Theme Packages', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_subtheme_section',
+			'page'     => 'general'
+		),
+		'bbp_settings_per_page' => array(
+			'title'    => esc_html__( 'Topics and Replies Per Page', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_per_page_section',
+			'page'     => 'reading'
+		),
+		'bbp_settings_per_rss_page' => array(
+			'title'    => esc_html__( 'Topics and Replies Per RSS Page', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_per_rss_page_section',
+			'page'     => 'reading',
+		),
+		'bbp_settings_root_slugs' => array(
+			'title'    => esc_html__( 'Forum Root Slug', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_root_slug_section',
+			'page'     => 'permalink'
+		),
+		'bbp_settings_single_slugs' => array(
+			'title'    => esc_html__( 'Forum Single Slugs', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_single_slug_section',
+			'page'     => 'permalink',
+		),
+		'bbp_settings_user_slugs' => array(
+			'title'    => esc_html__( 'Forum User Slugs', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_user_slug_section',
+			'page'     => 'permalink',
+		),
+
+		// Extend
+		'bbp_settings_buddypress' => array(
+			'title'    => esc_html__( 'Forum Integration for BuddyPress', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_buddypress_section',
+			'page'     => 'buddypress',
+		),
+		'bbp_settings_akismet' => array(
+			'title'    => esc_html__( 'Forum Integration for Akismet', 'bbpress' ),
+			'callback' => 'bbp_admin_setting_callback_akismet_section',
+			'page'     => 'discussion'
+		),
+
+		// Converter
+		'bbp_converter_connection' => array(
+			'title'    => esc_html__( 'Database Settings', 'bbpress' ),
+			'callback' => 'bbp_converter_setting_callback_main_section',
+			'page'     => 'converter'
+		),
+		'bbp_converter_options' => array(
+			'title'    => esc_html__( 'Options', 'bbpress' ),
+			'callback' => 'bbp_converter_setting_callback_options_section',
+			'page'     => 'converter'
+		)
+	) );
+}
+
+/**
+ * Get all of the settings fields.
+ *
+ * @since 2.1.0 bbPress (r4001)
+ *
+ * @return array
+ */
+function bbp_admin_get_settings_fields() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_admin_get_settings_fields', array(
+
+		/** User Section ******************************************************/
+
+		'bbp_settings_users' => array(
+
+			// Default role setting
+			'_bbp_default_role' => array(
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array()
+			),
+
+			// Allow global access
+			'_bbp_allow_global_access' => array(
+				'title'             => esc_html__( 'Roles', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_global_access',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow content throttling
+			'_bbp_allow_content_throttle' => array(
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Throttle setting
+			'_bbp_throttle_time' => array(
+				'title'             => esc_html__( 'Flooding', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_throttle',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow content editing
+			'_bbp_allow_content_edit' => array(
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Edit lock setting
+			'_bbp_edit_lock' => array(
+				'title'             => esc_html__( 'Editing', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_editlock',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow anonymous posting setting
+			'_bbp_allow_anonymous' => array(
+				'title'             => esc_html__( 'Anonymous', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_anonymous',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			)
+		),
+
+		/** Features Section **************************************************/
+
+		'bbp_settings_features' => array(
+
+			// Allow auto embedding setting
+			'_bbp_use_autoembed' => array(
+				'title'             => esc_html__( 'Auto-embed links', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_use_autoembed',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Set reply threading level
+			'_bbp_thread_replies_depth' => array(
+				'title'             => esc_html__( 'Reply Threading', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_thread_replies_depth',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow threaded replies
+			'_bbp_allow_threaded_replies' => array(
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow topic and reply revisions
+			'_bbp_allow_revisions' => array(
+				'title'             => esc_html__( 'Revisions', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_revisions',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow favorites setting
+			'_bbp_enable_favorites' => array(
+				'title'             => esc_html__( 'Favorites', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_favorites',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow subscriptions setting
+			'_bbp_enable_subscriptions' => array(
+				'title'             => esc_html__( 'Subscriptions', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_subscriptions',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow engagements setting
+			'_bbp_enable_engagements' => array(
+				'title'             => esc_html__( 'Engagements', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_engagements',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow topic tags
+			'_bbp_allow_topic_tags' => array(
+				'title'             => esc_html__( 'Topic tags', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topic_tags',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow topic tags
+			'_bbp_allow_search' => array(
+				'title'             => esc_html__( 'Search', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_search',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow fancy editor setting
+			'_bbp_use_wp_editor' => array(
+				'title'             => esc_html__( 'Post Formatting', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_use_wp_editor',
+				'args'              => array(),
+				'sanitize_callback' => 'intval'
+			),
+
+			// Allow per-forum moderators
+			'_bbp_allow_forum_mods' => array(
+				'title'             => esc_html__( 'Forum Moderators', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_forum_mods',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Allow moderators to edit users
+			'_bbp_allow_super_mods' => array(
+				'title'             => esc_html__( 'Super Moderators', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_super_mods',
+				'sanitize_callback' => 'intval',
+				'capability'        => 'edit_users',
+				'args'              => array()
+			)
+		),
+
+		/** Theme Packages ****************************************************/
+
+		'bbp_settings_theme_compat' => array(
+
+			// Theme package setting
+			'_bbp_theme_package_id' => array(
+				'title'             => esc_html__( 'Current Package', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_subtheme_id',
+				'sanitize_callback' => 'esc_sql',
+				'args'              => array()
+			)
+		),
+
+		/** Per Page Section **************************************************/
+
+		'bbp_settings_per_page' => array(
+
+			// Topics per page setting
+			'_bbp_topics_per_page' => array(
+				'title'             => esc_html__( 'Topics', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topics_per_page',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for' => '_bbp_topics_per_page' )
+			),
+
+			// Replies per page setting
+			'_bbp_replies_per_page' => array(
+				'title'             => esc_html__( 'Replies', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_replies_per_page',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for' => '_bbp_replies_per_page' )
+			)
+		),
+
+		/** Per RSS Page Section **********************************************/
+
+		'bbp_settings_per_rss_page' => array(
+
+			// Topics per page setting
+			'_bbp_topics_per_rss_page' => array(
+				'title'             => esc_html__( 'Topics', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topics_per_rss_page',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for' => '_bbp_topics_per_rss_page' )
+			),
+
+			// Replies per page setting
+			'_bbp_replies_per_rss_page' => array(
+				'title'             => esc_html__( 'Replies', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_replies_per_rss_page',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for' => '_bbp_replies_per_rss_page' )
+			)
+		),
+
+		/** Front Slugs *******************************************************/
+
+		'bbp_settings_root_slugs' => array(
+
+			// Root slug setting
+			'_bbp_root_slug' => array(
+				'title'             => esc_html__( 'Forum Root', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_root_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for' => '_bbp_root_slug' )
+			),
+
+			// Include root setting
+			'_bbp_include_root' => array(
+				'title'             => esc_html__( 'Forum Prefix', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_include_root',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// What to show on Forum Root
+			'_bbp_show_on_root' => array(
+				'title'             => esc_html__( 'Forum root should show', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_show_on_root',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=>'_bbp_show_on_root' )
+			),
+		),
+
+		/** Single Slugs ******************************************************/
+
+		'bbp_settings_single_slugs' => array(
+
+			// Forum slug setting
+			'_bbp_forum_slug' => array(
+				'title'             => esc_html__( 'Forum', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_forum_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_forum_slug' )
+			),
+
+			// Topic slug setting
+			'_bbp_topic_slug' => array(
+				'title'             => esc_html__( 'Topic', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topic_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_topic_slug' )
+			),
+
+			// Topic tag slug setting
+			'_bbp_topic_tag_slug' => array(
+				'title'             => esc_html__( 'Topic Tag', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topic_tag_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_topic_tag_slug' )
+			),
+
+			// View slug setting
+			'_bbp_view_slug' => array(
+				'title'             => esc_html__( 'Topic View', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_view_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_view_slug' )
+			),
+
+			// Reply slug setting
+			'_bbp_reply_slug' => array(
+				'title'             => _x( 'Reply', 'noun', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_reply_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_reply_slug' )
+			),
+
+			// Edit slug setting
+			'_bbp_edit_slug' => array(
+				'title'             => esc_html__( 'Edit', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_edit_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_edit_slug' )
+			),
+
+			// Search slug setting
+			'_bbp_search_slug' => array(
+				'title'             => esc_html__( 'Search', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_search_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_search_slug' )
+			)
+		),
+
+		/** User Slugs ********************************************************/
+
+		'bbp_settings_user_slugs' => array(
+
+			// User slug setting
+			'_bbp_user_slug' => array(
+				'title'             => esc_html__( 'User Base', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_user_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_user_slug' )
+			),
+
+			// Topics slug setting
+			'_bbp_topic_archive_slug' => array(
+				'title'             => esc_html__( 'Topics Started', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_topic_archive_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_topic_archive_slug' )
+			),
+
+			// Replies slug setting
+			'_bbp_reply_archive_slug' => array(
+				'title'             => esc_html__( 'Replies Created', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_reply_archive_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_reply_archive_slug' )
+			),
+
+			// Favorites slug setting
+			'_bbp_user_favs_slug' => array(
+				'title'             => esc_html__( 'Favorite Topics', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_user_favs_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_user_favs_slug' )
+			),
+
+			// Subscriptions slug setting
+			'_bbp_user_subs_slug' => array(
+				'title'             => esc_html__( 'Subscriptions', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_user_subs_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_user_subs_slug' )
+			),
+
+			// Engagements slug setting
+			'_bbp_user_engs_slug' => array(
+				'title'             => esc_html__( 'Engagements', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_user_engagements_slug',
+				'sanitize_callback' => 'bbp_sanitize_slug',
+				'args'              => array( 'label_for'=>'_bbp_user_engs_slug' )
+			)
+		),
+
+		/** BuddyPress ********************************************************/
+
+		'bbp_settings_buddypress' => array(
+
+			// Are group forums enabled?
+			'_bbp_enable_group_forums' => array(
+				'title'             => esc_html__( 'Group Forums', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_group_forums',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			),
+
+			// Group forums parent forum ID
+			'_bbp_group_forums_root_id' => array(
+				'title'             => esc_html__( 'Primary Forum', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_group_forums_root_id',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=>'_bbp_group_forums_root_id' )
+			)
+		),
+
+		/** Akismet ***********************************************************/
+
+		'bbp_settings_akismet' => array(
+
+			// Should we use Akismet
+			'_bbp_enable_akismet' => array(
+				'title'             => esc_html__( 'Use Akismet', 'bbpress' ),
+				'callback'          => 'bbp_admin_setting_callback_akismet',
+				'sanitize_callback' => 'intval',
+				'args'              => array()
+			)
+		),
+
+		/** Converter Page ****************************************************/
+
+		// Connection
+		'bbp_converter_connection' => array(
+
+			// System Select
+			'_bbp_converter_platform' => array(
+				'title'             => esc_html__( 'Select Platform', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_platform',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_platform' )
+			),
+
+			// Database Server
+			'_bbp_converter_db_server' => array(
+				'title'             => esc_html__( 'Database Server', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbserver',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_server' )
+			),
+
+			// Database Server Port
+			'_bbp_converter_db_port' => array(
+				'title'             => esc_html__( 'Database Port', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbport',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_port' )
+			),
+
+			// Database Name
+			'_bbp_converter_db_name' => array(
+				'title'             => esc_html__( 'Database Name', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbname',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_name' )
+			),
+
+			// Database User
+			'_bbp_converter_db_user' => array(
+				'title'             => esc_html__( 'Database User', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbuser',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_user' )
+			),
+
+			// Database Password
+			'_bbp_converter_db_pass' => array(
+				'title'             => esc_html__( 'Database Password', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbpass',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_pass' )
+			),
+
+			// Database Prefix
+			'_bbp_converter_db_prefix' => array(
+				'title'             => esc_html__( 'Table Prefix', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_dbprefix',
+				'sanitize_callback' => 'sanitize_text_field',
+				'args'              => array( 'label_for'=> '_bbp_converter_db_prefix' )
+			)
+		),
+
+		// Options
+		'bbp_converter_options' => array(
+
+			// Rows Limit
+			'_bbp_converter_rows' => array(
+				'title'             => esc_html__( 'Rows Limit', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_rows',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_rows' )
+			),
+
+			// Delay Time
+			'_bbp_converter_delay_time' => array(
+				'title'             => esc_html__( 'Delay Time', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_delay_time',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_delay_time' )
+			),
+
+			// Convert Users
+			'_bbp_converter_convert_users' => array(
+				'title'             => esc_html__( 'Convert Users', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_convert_users',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_convert_users' )
+			),
+
+			// Halt
+			'_bbp_converter_halt' => array(
+				'title'             => esc_html__( 'Stop on Error', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_halt',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_halt' )
+			),
+
+			// Restart
+			'_bbp_converter_restart' => array(
+				'title'             => esc_html__( 'Start Over', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_restart',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_restart' )
+			),
+
+			// Clean
+			'_bbp_converter_clean' => array(
+				'title'             => esc_html__( 'Helper Data', 'bbpress' ),
+				'callback'          => 'bbp_converter_setting_callback_clean',
+				'sanitize_callback' => 'intval',
+				'args'              => array( 'label_for'=> '_bbp_converter_clean' )
+			)
+		)
+	) );
+}
+
+/**
+ * Get settings fields by section.
+ *
+ * @since 2.1.0 bbPress (r4001)
+ *
+ * @param string $section_id ID of the section to get fields for
+ * @staticvar array $fields All of the available fields
+ * @return mixed False if section is invalid, array of fields otherwise.
+ */
+function bbp_admin_get_settings_fields_for_section( $section_id = '' ) {
+	static $fields = array();
+
+	// Default return value
+	$retval = array();
+
+	// Bail if section is empty
+	if ( empty( $section_id ) ) {
+		return false;
+	}
+
+	// Get all of the fields (so we can snag one section of them)
+	if ( empty( $fields ) ) {
+		$fields = bbp_admin_get_settings_fields();
+	}
+
+	// Get the field by section
+	if ( isset( $fields[ $section_id ] ) ) {
+		$retval = $fields[ $section_id ];
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_admin_get_settings_fields_for_section', $retval, $section_id );
+}
+
+/** User Section **************************************************************/
+
+/**
+ * User settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_user_section() {
+?>
+
+	<p><?php esc_html_e( 'Setting time limits and other user posting capabilities', 'bbpress' ); ?></p>
+
+<?php
+}
+
+
+/**
+ * Edit lock setting field
+ *
+ * @since 2.0.0 bbPress (r2737)
+ */
+function bbp_admin_setting_callback_editlock() {
+
+	// Start the output buffer for the second option
+	ob_start(); ?>
+
+	</label>
+	<label for="_bbp_edit_lock">
+		<input name="_bbp_edit_lock" id="_bbp_edit_lock" type="number" min="0" step="1" value="<?php bbp_form_option( '_bbp_edit_lock', '5' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_edit_lock' ); ?> />
+
+	<?php $select = ob_get_clean(); ?>
+
+	<label for="_bbp_allow_content_edit">
+		<input name="_bbp_allow_content_edit" id="_bbp_allow_content_edit" type="checkbox" value="1" <?php checked( bbp_allow_content_edit( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_content_edit' ); ?> />
+		<?php printf( esc_html__( 'Allow users to edit their content for %s minutes after posting', 'bbpress' ), $select ); ?>
+	</label>
+	<p class="description"><?php esc_html_e( 'If checked, setting to "0 minutes" allows editing forever.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Throttle setting field
+ *
+ * @since 2.0.0 bbPress (r2737)
+ */
+function bbp_admin_setting_callback_throttle() {
+
+	// Start the output buffer for the second option
+	ob_start(); ?>
+
+	</label>
+	<label for="_bbp_throttle_time">
+		<input name="_bbp_throttle_time" id="_bbp_throttle_time" type="number" min="0" step="1" value="<?php bbp_form_option( '_bbp_throttle_time', '10' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_throttle_time' ); ?> />
+
+	<?php $select = ob_get_clean(); ?>
+
+	<label for="_bbp_allow_content_throttle">
+		<input name="_bbp_allow_content_throttle" id="_bbp_allow_content_throttle" type="checkbox" value="1" <?php checked( bbp_allow_content_throttle( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_content_throttle' ); ?> />
+		<?php printf( esc_html__( 'Allow flood protection by throttling users for %s seconds after posting', 'bbpress' ), $select ); ?>
+	</label>
+	<p class="description"><?php esc_html_e( 'Use this to discourage users from spamming your forums.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow anonymous posting setting field
+ *
+ * @since 2.0.0 bbPress (r2737)
+ */
+function bbp_admin_setting_callback_anonymous() {
+?>
+
+	<input name="_bbp_allow_anonymous" id="_bbp_allow_anonymous" type="checkbox" value="1" <?php checked( bbp_allow_anonymous( false ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_anonymous' ); ?> />
+	<label for="_bbp_allow_anonymous"><?php esc_html_e( 'Allow guest users without accounts to create topics and replies', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'Works best on intranets or paired with antispam measures like Akismet.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow global access setting field
+ *
+ * @since 2.0.0 bbPress (r3378)
+ */
+function bbp_admin_setting_callback_global_access() {
+
+	// Get the default role once rather than loop repeatedly below
+	$default_role = bbp_get_default_role();
+	$roles        = bbp_get_dynamic_roles();
+
+	// Start the output buffer for the select dropdown
+	ob_start(); ?>
+
+	</label>
+	<label for="_bbp_default_role">
+		<select name="_bbp_default_role" id="_bbp_default_role" <?php bbp_maybe_admin_setting_disabled( '_bbp_default_role' ); ?>>
+		<?php foreach ( $roles as $role => $details ) : ?>
+
+			<option <?php selected( $default_role, $role ); ?> value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+
+		<?php endforeach; ?>
+		</select>
+
+	<?php $select = ob_get_clean(); ?>
+
+	<label for="_bbp_allow_global_access">
+		<input name="_bbp_allow_global_access" id="_bbp_allow_global_access" type="checkbox" value="1" <?php checked( bbp_allow_global_access( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_global_access' ); ?> />
+		<?php printf( esc_html__( 'Automatically give registered visitors the %s forum role', 'bbpress' ), $select ); ?>
+	</label>
+	<p class="description"><?php esc_html_e( 'Uncheck this to manually assign all user access to your forums.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/** Features Section **********************************************************/
+
+/**
+ * Features settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_features_section() {
+?>
+
+	<p><?php esc_html_e( 'Forum features that can be toggled on and off', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow favorites setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_favorites() {
+?>
+
+	<input name="_bbp_enable_favorites" id="_bbp_enable_favorites" type="checkbox" value="1" <?php checked( bbp_is_favorites_active( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_enable_favorites' ); ?> />
+	<label for="_bbp_enable_favorites"><?php esc_html_e( 'Allow users to mark topics as favorites', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow subscriptions setting field
+ *
+ * @since 2.0.0 bbPress (r2737)
+ */
+function bbp_admin_setting_callback_subscriptions() {
+?>
+
+	<input name="_bbp_enable_subscriptions" id="_bbp_enable_subscriptions" type="checkbox" value="1" <?php checked( bbp_is_subscriptions_active( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_enable_subscriptions' ); ?> />
+	<label for="_bbp_enable_subscriptions"><?php esc_html_e( 'Allow users to subscribe to forums and topics', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow engagements setting field
+ *
+ * @since 2.0.0 bbPress (r2737)
+ */
+function bbp_admin_setting_callback_engagements() {
+?>
+
+	<input name="_bbp_enable_engagements" id="_bbp_enable_engagements" type="checkbox" value="1" <?php checked( bbp_is_engagements_active( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_enable_engagements' ); ?> />
+	<label for="_bbp_enable_engagements"><?php esc_html_e( 'Allow tracking of topics each user engages in', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow topic tags setting field
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+function bbp_admin_setting_callback_topic_tags() {
+?>
+
+	<input name="_bbp_allow_topic_tags" id="_bbp_allow_topic_tags" type="checkbox" value="1" <?php checked( bbp_allow_topic_tags( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_topic_tags' ); ?> />
+	<label for="_bbp_allow_topic_tags"><?php esc_html_e( 'Allow topics to have tags', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow forum-mods setting field
+ *
+ * @since 2.6.0 bbPress (r5834)
+ */
+function bbp_admin_setting_callback_forum_mods() {
+?>
+
+	<input name="_bbp_allow_forum_mods" id="_bbp_allow_forum_mods" type="checkbox" value="1" <?php checked( bbp_allow_forum_mods( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_forum_mods' ); ?> />
+	<label for="_bbp_allow_forum_mods"><?php esc_html_e( 'Allow forums to have dedicated moderators', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'This does not include the ability to edit users.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow super-mods setting field
+ *
+ * @since 2.6.0 bbPress (r6562)
+ */
+function bbp_admin_setting_callback_super_mods() {
+?>
+
+	<input name="_bbp_allow_super_mods" id="_bbp_allow_super_mods" type="checkbox" value="1" <?php checked( bbp_allow_super_mods( false ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_super_mods' ); ?> />
+	<label for="_bbp_allow_super_mods"><?php esc_html_e( 'Allow Moderators and Keymasters to edit users', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'This includes roles, passwords, and email addresses.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow forum wide search
+ *
+ * @since 2.4.0 bbPress (r4970)
+ */
+function bbp_admin_setting_callback_search() {
+?>
+
+	<input name="_bbp_allow_search" id="_bbp_allow_search" type="checkbox" value="1" <?php checked( bbp_allow_search( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_search' ); ?> />
+	<label for="_bbp_allow_search"><?php esc_html_e( 'Allow forum wide search', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Hierarchical reply maximum depth level setting field
+ *
+ * Replies will be threaded if depth is 2 or greater
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+function bbp_admin_setting_callback_thread_replies_depth() {
+
+	// Set maximum depth for dropdown
+	$max_depth     = (int) apply_filters( 'bbp_thread_replies_depth_max', 10 );
+	$current_depth = bbp_thread_replies_depth();
+
+	// Start an output buffer for the select dropdown
+	ob_start(); ?>
+
+	</label>
+	<label for="_bbp_thread_replies_depth">
+		<select name="_bbp_thread_replies_depth" id="_bbp_thread_replies_depth" <?php bbp_maybe_admin_setting_disabled( '_bbp_thread_replies_depth' ); ?>>
+		<?php for ( $i = 2; $i <= $max_depth; $i++ ) : ?>
+
+			<option value="<?php echo esc_attr( $i ); ?>" <?php selected( $i, $current_depth ); ?>><?php echo esc_html( $i ); ?></option>
+
+		<?php endfor; ?>
+		</select>
+
+	<?php $select = ob_get_clean(); ?>
+
+	<label for="_bbp_allow_threaded_replies">
+		<input name="_bbp_allow_threaded_replies" id="_bbp_allow_threaded_replies" type="checkbox" value="1" <?php checked( '1', bbp_allow_threaded_replies( false ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_threaded_replies' ); ?> />
+		<?php printf( esc_html__( 'Enable threaded (nested) replies %s levels deep', 'bbpress' ), $select ); ?>
+	</label>
+
+<?php
+}
+
+/**
+ * Allow topic and reply revisions
+ *
+ * @since 2.0.0 bbPress (r3412)
+ */
+function bbp_admin_setting_callback_revisions() {
+?>
+
+	<input name="_bbp_allow_revisions" id="_bbp_allow_revisions" type="checkbox" value="1" <?php checked( bbp_allow_revisions( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_allow_revisions' ); ?> />
+	<label for="_bbp_allow_revisions"><?php esc_html_e( 'Allow topic and reply revision logging', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Use the WordPress editor setting field
+ *
+ * @since 2.1.0 bbPress (r3586)
+ */
+function bbp_admin_setting_callback_use_wp_editor() {
+?>
+
+	<input name="_bbp_use_wp_editor" id="_bbp_use_wp_editor" type="checkbox" value="1" <?php checked( bbp_use_wp_editor( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_use_wp_editor' ); ?> />
+	<label for="_bbp_use_wp_editor"><?php esc_html_e( 'Add toolbar & buttons to textareas to help with HTML formatting', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Main subtheme section
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_subtheme_section() {
+?>
+
+	<p><?php esc_html_e( 'How your forum content is displayed within your existing theme.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Use the WordPress editor setting field
+ *
+ * @since 2.1.0 bbPress (r3586)
+ */
+function bbp_admin_setting_callback_subtheme_id() {
+
+	// Declare locale variable
+	$theme_options   = '';
+	$current_package = bbp_get_theme_package_id( 'default' );
+
+	// Note: This should never be empty. /templates/ is the
+	// canonical backup if no other packages exist. If there's an error here,
+	// something else is wrong.
+	//
+	// @see bbPress::register_theme_packages()
+	foreach ( (array) bbpress()->theme_compat->packages as $id => $theme ) {
+		$theme_options .= '<option value="' . esc_attr( $id ) . '"' . selected( $theme->id, $current_package, false ) . '>' . esc_html( $theme->name ) . '</option>';
+	}
+
+	if ( ! empty( $theme_options ) ) : ?>
+
+		<select name="_bbp_theme_package_id" id="_bbp_theme_package_id" <?php bbp_maybe_admin_setting_disabled( '_bbp_theme_package_id' ); ?>><?php echo $theme_options; ?></select>
+		<label for="_bbp_theme_package_id"><?php esc_html_e( 'will serve all bbPress templates', 'bbpress' ); ?></label>
+
+	<?php else : ?>
+
+		<p><?php esc_html_e( 'No template packages available.', 'bbpress' ); ?></p>
+
+	<?php endif;
+}
+
+/**
+ * Allow oEmbed in replies
+ *
+ * @since 2.1.0 bbPress (r3752)
+ */
+function bbp_admin_setting_callback_use_autoembed() {
+?>
+
+	<input name="_bbp_use_autoembed" id="_bbp_use_autoembed" type="checkbox" value="1" <?php checked( bbp_use_autoembed( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_use_autoembed' ); ?> />
+	<label for="_bbp_use_autoembed"><?php esc_html_e( 'Embed media (YouTube, Twitter, Flickr, etc...) directly into topics and replies', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/** Per Page Section **********************************************************/
+
+/**
+ * Per page settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_per_page_section() {
+?>
+
+	<p><?php esc_html_e( 'How many topics and replies to show per page', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Topics per page setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_topics_per_page() {
+?>
+
+	<input name="_bbp_topics_per_page" id="_bbp_topics_per_page" type="number" min="1" step="1" value="<?php bbp_form_option( '_bbp_topics_per_page', '15' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_topics_per_page' ); ?> />
+	<?php esc_html_e( 'per page', 'bbpress' ); ?>
+
+<?php
+}
+
+/**
+ * Replies per page setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_replies_per_page() {
+?>
+
+	<input name="_bbp_replies_per_page" id="_bbp_replies_per_page" type="number" min="1" step="1" value="<?php bbp_form_option( '_bbp_replies_per_page', '15' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_replies_per_page' ); ?> />
+	<?php esc_html_e( 'per page', 'bbpress' ); ?>
+
+<?php
+}
+
+/** Per RSS Page Section ******************************************************/
+
+/**
+ * Per page settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_per_rss_page_section() {
+?>
+
+	<p><?php esc_html_e( 'How many topics and replies to show per RSS page', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Topics per RSS page setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_topics_per_rss_page() {
+?>
+
+	<input name="_bbp_topics_per_rss_page" id="_bbp_topics_per_rss_page" type="number" min="1" step="1" value="<?php bbp_form_option( '_bbp_topics_per_rss_page', '25' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_topics_per_rss_page' ); ?> />
+	<?php esc_html_e( 'per page', 'bbpress' ); ?>
+
+<?php
+}
+
+/**
+ * Replies per RSS page setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_replies_per_rss_page() {
+?>
+
+	<input name="_bbp_replies_per_rss_page" id="_bbp_replies_per_rss_page" type="number" min="1" step="1" value="<?php bbp_form_option( '_bbp_replies_per_rss_page', '25' ); ?>" class="small-text"<?php bbp_maybe_admin_setting_disabled( '_bbp_replies_per_rss_page' ); ?> />
+	<?php esc_html_e( 'per page', 'bbpress' ); ?>
+
+<?php
+}
+
+/** Slug Section **************************************************************/
+
+/**
+ * Slugs settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_root_slug_section() {
+
+	// Flush rewrite rules when this section is saved
+	if ( isset( $_GET['settings-updated'] ) && isset( $_GET['page'] ) ) {
+		flush_rewrite_rules();
+	} ?>
+
+	<p><?php esc_html_e( 'Customize your Forums root. Partner with a WordPress Page and use Shortcodes for more flexibility.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Root slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_root_slug() {
+?>
+
+	<input name="_bbp_root_slug" id="_bbp_root_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_root_slug', 'forums', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_root_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_root_slug', 'forums' );
+}
+
+/**
+ * Include root slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_include_root() {
+?>
+
+	<input name="_bbp_include_root" id="_bbp_include_root" type="checkbox" value="1" <?php checked( bbp_include_root_slug() ); bbp_maybe_admin_setting_disabled( '_bbp_include_root' ); ?> />
+	<label for="_bbp_include_root"><?php esc_html_e( 'Prefix all forum content with the Forum Root slug (Recommended)', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Include root slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_show_on_root() {
+
+	// Current setting
+	$show_on_root = bbp_show_on_root();
+
+	// Options for forum root output
+	$root_options = array(
+		'forums' => array(
+			'name' => esc_attr__( 'Forum Index', 'bbpress' )
+		),
+		'topics' => array(
+			'name' => esc_attr__( 'Topics by Last Post', 'bbpress' )
+		)
+	); ?>
+
+	<select name="_bbp_show_on_root" id="_bbp_show_on_root" <?php bbp_maybe_admin_setting_disabled( '_bbp_show_on_root' ); ?>>
+
+		<?php foreach ( $root_options as $option_id => $details ) : ?>
+
+			<option <?php selected( $show_on_root, $option_id ); ?> value="<?php echo esc_attr( $option_id ); ?>"><?php echo esc_html( $details['name'] ); ?></option>
+
+		<?php endforeach; ?>
+
+	</select>
+
+	<?php
+
+	// Look for theme support
+	$forum_archive = basename( bbp_get_forum_archive_template() );
+
+	// This setting doesn't work if the theme has an archive-forum.php template.
+	if ( ! empty( $forum_archive ) ) : ?>
+
+		<p class="description"><?php printf( esc_html__( 'This setting will be ignored because %s was found in your theme.', 'bbpress' ), '<code>' . $forum_archive . '</code>' ); ?></p>
+
+	<?php endif;
+}
+
+/** User Slug Section *********************************************************/
+
+/**
+ * Slugs settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_user_slug_section() {
+?>
+
+	<p><?php esc_html_e( 'Customize your user profile slugs.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * User slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_user_slug() {
+?>
+
+	<input name="_bbp_user_slug" id="_bbp_user_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_user_slug', 'users', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_user_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_user_slug', 'users' );
+}
+
+/**
+ * Topic archive slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_topic_archive_slug() {
+?>
+
+	<input name="_bbp_topic_archive_slug" id="_bbp_topic_archive_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_topic_archive_slug', 'topics', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_topic_archive_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_topic_archive_slug', 'topics' );
+}
+
+/**
+ * Reply archive slug setting field
+ *
+ * @since 2.4.0 bbPress (r4932)
+ */
+function bbp_admin_setting_callback_reply_archive_slug() {
+?>
+
+	<input name="_bbp_reply_archive_slug" id="_bbp_reply_archive_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_reply_archive_slug', 'replies', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_reply_archive_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_reply_archive_slug', 'replies' );
+}
+
+/**
+ * Favorites slug setting field
+ *
+ * @since 2.4.0 bbPress (r4932)
+ */
+function bbp_admin_setting_callback_user_favs_slug() {
+?>
+
+	<input name="_bbp_user_favs_slug" id="_bbp_user_favs_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_user_favs_slug', 'favorites', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_user_favs_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_user_favs_slug', 'favorites' );
+}
+
+/**
+ * Subscriptions slug setting field
+ *
+ * @since 2.4.0 bbPress (r4932)
+ */
+function bbp_admin_setting_callback_user_subs_slug() {
+?>
+
+	<input name="_bbp_user_subs_slug" id="_bbp_user_subs_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_user_subs_slug', 'subscriptions', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_user_subs_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_user_subs_slug', 'subscriptions' );
+}
+
+/**
+ * Engagements slug setting field
+ *
+ * @since 2.6.0 bbPress (r6320)
+ */
+function bbp_admin_setting_callback_user_engagements_slug() {
+?>
+
+	<input name="_bbp_user_engs_slug" id="_bbp_user_engs_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_user_engs_slug', 'engagements', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_user_engs_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_user_engs_slug', 'engagements' );
+}
+
+/** Single Slugs **************************************************************/
+
+/**
+ * Slugs settings section description for the settings page
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_single_slug_section() {
+?>
+
+	<p><?php printf( esc_html__( 'Custom slugs for single forums, topics, replies, tags, views, edit, and search.', 'bbpress' ), get_admin_url( null, 'options-permalink.php' ) ); ?></p>
+
+<?php
+}
+
+/**
+ * Forum slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_forum_slug() {
+?>
+
+	<input name="_bbp_forum_slug" id="_bbp_forum_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_forum_slug', 'forum', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_forum_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_forum_slug', 'forum' );
+}
+
+/**
+ * Topic slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_topic_slug() {
+?>
+
+	<input name="_bbp_topic_slug" id="_bbp_topic_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_topic_slug', 'topic', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_topic_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_topic_slug', 'topic' );
+}
+
+/**
+ * Reply slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_reply_slug() {
+?>
+
+	<input name="_bbp_reply_slug" id="_bbp_reply_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_reply_slug', 'reply', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_reply_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_reply_slug', 'reply' );
+}
+
+/**
+ * Topic tag slug setting field
+ *
+ * @since 2.0.0 bbPress (r2786)
+ */
+function bbp_admin_setting_callback_topic_tag_slug() {
+?>
+
+	<input name="_bbp_topic_tag_slug" id="_bbp_topic_tag_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_topic_tag_slug', 'topic-tag', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_topic_tag_slug' ); ?> />
+
+<?php
+
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_topic_tag_slug', 'topic-tag' );
+}
+
+/**
+ * View slug setting field
+ *
+ * @since 2.0.0 bbPress (r2789)
+ */
+function bbp_admin_setting_callback_view_slug() {
+?>
+
+	<input name="_bbp_view_slug" id="_bbp_view_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_view_slug', 'view', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_view_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_view_slug', 'view' );
+}
+
+/**
+ * Search slug setting field
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+function bbp_admin_setting_callback_search_slug() {
+?>
+
+	<input name="_bbp_search_slug" id="_bbp_search_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_search_slug', 'search', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_search_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_search_slug', 'search' );
+}
+
+/**
+ * Edit slug setting field
+ *
+ * @since 2.6.2 bbPress (r6965)
+ */
+function bbp_admin_setting_callback_edit_slug() {
+?>
+
+	<input name="_bbp_edit_slug" id="_bbp_edit_slug" type="text" class="regular-text code" value="<?php bbp_form_option( '_bbp_edit_slug', 'edit', true ); ?>"<?php bbp_maybe_admin_setting_disabled( '_bbp_edit_slug' ); ?> />
+
+<?php
+	// Slug Check
+	bbp_form_slug_conflict_check( '_bbp_edit_slug', 'edit' );
+}
+
+/** BuddyPress ****************************************************************/
+
+/**
+ * Extension settings section description for the settings page
+ *
+ * @since 2.1.0 bbPress (r3575)
+ */
+function bbp_admin_setting_callback_buddypress_section() {
+?>
+
+	<p><?php esc_html_e( 'Forum settings for BuddyPress', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Allow BuddyPress group forums setting field
+ *
+ * @since 2.1.0 bbPress (r3575)
+ */
+function bbp_admin_setting_callback_group_forums() {
+?>
+
+	<input name="_bbp_enable_group_forums" id="_bbp_enable_group_forums" type="checkbox" value="1" <?php checked( bbp_is_group_forums_active( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_enable_group_forums' ); ?> />
+	<label for="_bbp_enable_group_forums"><?php esc_html_e( 'Allow BuddyPress Groups to have their own forums', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Replies per page setting field
+ *
+ * @since 2.1.0 bbPress (r3575)
+ */
+function bbp_admin_setting_callback_group_forums_root_id() {
+
+	// Group root ID
+	$group_root = bbp_get_group_forums_root_id();
+	if ( ! bbp_get_forum( $group_root ) ) {
+		delete_option( '_bbp_group_forums_root_id' );
+		$group_root = 0;
+	}
+
+	// Output the dropdown for all forums
+	$select = bbp_get_dropdown( array(
+		'selected'           => $group_root,
+		'show_none'          => esc_html__( '&mdash; No parent &mdash;', 'bbpress' ),
+		'orderby'            => 'title',
+		'order'              => 'ASC',
+		'select_id'          => '_bbp_group_forums_root_id',
+		'disable_categories' => false,
+		'disabled'           => '_bbp_group_forums_root_id'
+	) );
+
+	// Check cap one time
+	$can_add_new = current_user_can( 'publish_forums' );
+	$button = '';
+
+	// Text variations based on configuration
+	if ( empty( $group_root ) && ( true === $can_add_new ) ) {
+
+		// New URL
+		$new_url = wp_nonce_url( add_query_arg( array(
+			'page'   => 'bbpress',
+			'create' => 'bbp-group-forum-root'
+		), admin_url( 'options-general.php' ) ), '_bbp_group_forums_root_id' );
+
+		// Button & text
+		$button = '<a href="' . esc_url( $new_url ) . '">' . esc_html__( 'create a new one', 'bbpress' ) . '</a>';
+		$text   = esc_html__( 'Use %s to contain your group forums, or %s', 'bbpress' ); //phpcs:ignore
+	} else {
+		$text = esc_html__( 'Use %s to contain your group forums', 'bbpress' );
+	}
+
+	// Output
+	printf( $text, $select, $button ); ?>
+	<p class="description"><?php esc_html_e( 'Changing this will not move existing forums.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/** Akismet *******************************************************************/
+
+/**
+ * Extension settings section description for the settings page
+ *
+ * @since 2.1.0 bbPress (r3575)
+ */
+function bbp_admin_setting_callback_akismet_section() {
+?>
+
+	<p><?php esc_html_e( 'Forum settings for Akismet', 'bbpress' ); ?></p>
+
+<?php
+}
+
+
+/**
+ * Allow Akismet setting field
+ *
+ * @since 2.1.0 bbPress (r3575)
+ */
+function bbp_admin_setting_callback_akismet() {
+?>
+
+	<input name="_bbp_enable_akismet" id="_bbp_enable_akismet" type="checkbox" value="1" <?php checked( bbp_is_akismet_active( true ) ); bbp_maybe_admin_setting_disabled( '_bbp_enable_akismet' ); ?> />
+	<label for="_bbp_enable_akismet"><?php esc_html_e( 'Allow Akismet to actively prevent forum spam.', 'bbpress' ); ?></label>
+
+<?php
+}
+
+/** Settings Page *************************************************************/
+
+/**
+ * The main settings page
+ *
+ * @since 2.0.0 bbPress (r2643)
+ */
+function bbp_admin_settings() {
+?>
+
+	<div class="wrap">
+		<h1 class="wp-heading-inline"><?php esc_html_e( 'Forums Settings', 'bbpress' ); ?></h1>
+		<hr class="wp-header-end">
+
+		<form action="options.php" method="post">
+
+			<?php settings_fields( 'bbpress' ); ?>
+
+			<?php do_settings_sections( 'bbpress' ); ?>
+
+			<p class="submit">
+				<input type="submit" name="submit" class="button-primary" value="<?php esc_attr_e( 'Save Changes', 'bbpress' ); ?>" />
+			</p>
+		</form>
+	</div>
+
+<?php
+}
+
+/** Converter Section *********************************************************/
+
+/**
+ * Main settings section description for the settings page
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_main_section() {
+?>
+
+	<p><?php _e( 'Information about the database for your previous forums so they can be converted.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Platform setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_platform() {
+
+	// Converters
+	$current    = get_option( '_bbp_converter_platform' );
+	$converters = bbp_get_converters();
+	$options    = '';
+
+	// Put options together
+	foreach ( $converters as $name => $file ) {
+		$options .= '<option value="' . esc_attr( $name ) . '"' . selected( $name, $current, false ) . '>' . esc_html( $name ) . '</option>';
+	} ?>
+
+	<select name="_bbp_converter_platform" id="_bbp_converter_platform"><?php echo $options; ?></select>
+	<p class="description"><?php esc_html_e( 'The previous forum software', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Database Server setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbserver() {
+?>
+
+	<input name="_bbp_converter_db_server" id="_bbp_converter_db_server" type="text" class="code" value="<?php bbp_form_option( '_bbp_converter_db_server', 'localhost' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_server' ); ?> />
+	<p class="description"><?php printf( esc_html__( 'Use default %s if same server, or IP or hostname', 'bbpress' ), '<code>localhost</code>' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Database Server Port setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbport() {
+?>
+
+	<input name="_bbp_converter_db_port" id="_bbp_converter_db_port" type="text" class="code" value="<?php bbp_form_option( '_bbp_converter_db_port', '3306' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_port' ); ?> />
+	<p class="description"><?php printf( esc_html__( 'Use default %s if unsure', 'bbpress' ), '<code>3306</code>' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Database User setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbuser() {
+?>
+
+	<input name="_bbp_converter_db_user" id="_bbp_converter_db_user" type="text" class="code" value="<?php bbp_form_option( '_bbp_converter_db_user' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_user' ); ?> />
+	<p class="description"><?php esc_html_e( 'User to access the database', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Database Pass setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbpass() {
+?>
+
+	<span class="bbp-converter-db-password-wrapper">
+		<input name="_bbp_converter_db_pass" id="_bbp_converter_db_pass" class="bbp-converter-db-pass code" type="password" value="<?php bbp_form_option( '_bbp_converter_db_pass' ); ?>" autocomplete="off" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_pass' ); ?> />
+		<button type="button" class="bbp-db-pass-toggle password">
+			<span class="screen-reader-text"><?php esc_html_e( 'Toggle', 'bbpress' ); ?></span>
+			<span class="toggle-indicator" aria-hidden="true"></span>
+		</button>
+	</span>
+	<p class="description"><?php esc_html_e( 'Password for the above database user', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Database Name setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbname() {
+?>
+
+	<input name="_bbp_converter_db_name" id="_bbp_converter_db_name" type="text" class="code" value="<?php bbp_form_option( '_bbp_converter_db_name' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_name' ); ?> />
+	<p class="description"><?php esc_html_e( 'Name of the database with your old forum data', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Main settings section description for the settings page
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_options_section() {
+?>
+
+	<p><?php esc_html_e( 'Some optional parameters to help tune the conversion process.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Table Prefix setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_dbprefix() {
+?>
+
+	<input name="_bbp_converter_db_prefix" id="_bbp_converter_db_prefix" type="text" class="code" value="<?php bbp_form_option( '_bbp_converter_db_prefix' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_db_prefix' ); ?> />
+	<p class="description"><?php printf( esc_html__( 'Use %s if converting from BuddyPress Legacy', 'bbpress' ), '<code>wp_bb_</code>' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Rows Limit setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_rows() {
+?>
+
+	<input name="_bbp_converter_rows" id="_bbp_converter_rows" type="number" min="1" max="5000" value="<?php bbp_form_option( '_bbp_converter_rows', '100' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_rows' ); ?> />
+	<label for="_bbp_converter_rows"><?php esc_html_e( 'entry maximum when querying for data to convert', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'Keep this low if you experience out-of-memory issues.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Delay Time setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_delay_time() {
+?>
+
+	<input name="_bbp_converter_delay_time" id="_bbp_converter_delay_time" type="number" min="2" max="3600" value="<?php bbp_form_option( '_bbp_converter_delay_time', '2' ); ?>" <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_delay_time' ); ?> />
+	<label for="_bbp_converter_delay_time"><?php esc_html_e( 'second delay between each query of rows above', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'Keep this high to prevent too-many-connection issues.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Halt setting field
+ *
+ * @since 2.6.0 bbPress (r6599)
+ */
+function bbp_converter_setting_callback_halt() {
+?>
+
+	<input name="_bbp_converter_halt" id="_bbp_converter_halt" type="checkbox" value="1" <?php checked( get_option( '_bbp_converter_halt', false ) ); ?> />
+	<label for="_bbp_converter_halt"><?php esc_html_e( 'Halt the conversion if an error occurs', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'This is helpful if you want to debug problems.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+
+/**
+ * Edit Restart setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_restart() {
+?>
+
+	<input name="_bbp_converter_restart" id="_bbp_converter_restart" type="checkbox" value="1" <?php checked( get_option( '_bbp_converter_restart', false ) ); ?> />
+	<label for="_bbp_converter_restart"><?php esc_html_e( 'Restart the converter from the beginning', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'This forces all steps back to 0. Avoid duplicate data by purging or resetting first.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Clean setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_clean() {
+?>
+
+	<input name="_bbp_converter_clean" id="_bbp_converter_clean" type="checkbox" value="1" <?php checked( get_option( '_bbp_converter_clean', false ) ); ?> />
+	<label for="_bbp_converter_clean"><?php esc_html_e( 'Purge all meta-data from a previous import', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'Use this if an import failed, or you just want to remove the relationship data.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Edit Convert Users setting field
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_converter_setting_callback_convert_users() {
+?>
+
+	<input name="_bbp_converter_convert_users" id="_bbp_converter_convert_users" type="checkbox" value="1" <?php checked( get_option( '_bbp_converter_convert_users', false ) ); ?> <?php bbp_maybe_admin_setting_disabled( '_bbp_converter_convert_users' ); ?> />
+	<label for="_bbp_converter_convert_users"><?php esc_html_e( 'Import user accounts from previous forums', 'bbpress' ); ?></label>
+	<p class="description"><?php esc_html_e( 'Passwords remain encrypted, and are converted as individual users log in.', 'bbpress' ); ?></p>
+
+<?php
+}
+
+/** Converter Page ************************************************************/
+
+/**
+ * The main settings page
+ *
+ * @since 2.1.0 bbPress (r3186)
+ */
+function bbp_converter_settings_page() {
+
+	// Status
+	$step = (int) get_option( '_bbp_converter_step', 0 );
+	$max  = (int) bbp_admin()->converter->max_steps;
+
+	// Starting or continuing?
+	$status_text = ! empty( $step )
+		? sprintf( esc_html__( 'Up next: step %s', 'bbpress' ), $step )
+		: esc_html__( 'Ready', 'bbpress' );
+
+	// Starting or continuing?
+	$start_text = ! empty( $step )
+		? esc_html__( 'Resume', 'bbpress' )
+		: esc_html__( 'Start',  'bbpress' );
+
+	// Starting or continuing?
+	$progress_text = ! empty( $step )
+		? sprintf( esc_html__( 'Previously stopped at step %1$d of %2$d', 'bbpress' ), $step, $max )
+		: esc_html__( 'Ready to go.', 'bbpress' ); ?>
+
+	<div class="wrap">
+		<h1 class="wp-heading-inline"><?php esc_html_e( 'Forum Tools', 'bbpress' ); ?></h1>
+		<hr class="wp-header-end">
+		<h2 class="nav-tab-wrapper"><?php bbp_tools_admin_tabs( 'bbp-converter' ); ?></h2>
+
+		<div class="bbp-converter-wrap">
+			<div id="poststuff" class="bbp-converter-monitor-wrap">
+				<div id="post-body" class="metabox-holder columns-1">
+					<div id="postbox-container-1" class="postbox-container">
+						<div id="normal-sortables" class="meta-box-sortables ui-sortable">
+							<div id="bbp-converter-monitor" class="postbox">
+								<button type="button" class="handlediv" aria-expanded="true">
+									<span class="screen-reader-text"><?php esc_html_e( 'Toggle panel: Import Status', 'bbpress' ); ?></span>
+									<span class="toggle-indicator" aria-hidden="true"></span>
+								</button>
+								<h2 class="hndle ui-sortable-handle">
+									<span><?php esc_html_e( 'Import Monitor', 'bbpress' ); ?></span>
+									<span id="bbp-converter-status"><?php echo esc_html( $status_text ); ?></span>
+									<span id="bbp-converter-step-percentage" class="bbp-progress-bar"></span>
+									<span id="bbp-converter-total-percentage" class="bbp-progress-bar"></span>
+								</h2>
+								<div class="inside">
+									<div id="bbp-converter-message" class="bbp-converter-log">
+										<p><?php echo esc_html( $progress_text ); ?></p>
+									</div>
+								</div>
+								<div class="actions">
+									<input type="button" name="submit" class="button-primary" id="bbp-converter-start" value="<?php echo esc_attr( $start_text ); ?>" />
+									<input type="button" name="submit" class="button-primary" id="bbp-converter-stop" value="<?php esc_attr_e( 'Pause', 'bbpress' ); ?>" />
+									<span class="spinner" id="bbp-converter-spinner"></span>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<form action="#" method="post" id="bbp-converter-settings" class="bbp-converter-settings-wrap"><?php
+
+				// Fields
+				settings_fields( 'converter' );
+
+				// Sections
+				do_settings_sections( 'converter' );
+
+			?></form>
+		</div>
+	</div>
+<?php
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Contextual help for Forums settings page
+ *
+ * @since 2.0.0 bbPress (r3119)
+ */
+function bbp_admin_settings_help() {
+
+	$current_screen = get_current_screen();
+
+	// Bail if current screen could not be found
+	if ( empty( $current_screen ) ) {
+		return;
+	}
+
+	// Overview
+	$current_screen->add_help_tab( array(
+		'id'      => 'overview',
+		'title'   => esc_html__( 'Overview', 'bbpress' ),
+		'content' => '<p>' . esc_html__( 'This screen provides access to all of the Forums settings.',                          'bbpress' ) . '</p>' .
+					 '<p>' . esc_html__( 'Please see the additional help tabs for more information on each individual section.', 'bbpress' ) . '</p>'
+	) );
+
+	// Main Settings
+	$current_screen->add_help_tab( array(
+		'id'      => 'main_settings',
+		'title'   => esc_html__( 'Main Settings', 'bbpress' ),
+		'content' => '<p>' . esc_html__( 'The "Main Settings" section includes a number of options:', 'bbpress' ) . '</p>' .
+					 '<p>' .
+						'<ul>' .
+							'<li>' . esc_html__( 'You can choose to lock a post after a certain number of minutes. "Locking post editing" will prevent the author from editing some amount of time after saving a post.',              'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( '"Throttle time" is the amount of time required between posts from a single author. The higher the throttle time, the longer a user will need to wait between posting to the forum.', 'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Favorites are a way for users to save and later return to topics they favor. This is enabled by default.',                                                                           'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Subscriptions allow users to subscribe for notifications to topics that interest them. This is enabled by default.',                                                                 'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Topic-Tags allow users to filter topics between forums. This is enabled by default.',                                                                                                'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( '"Anonymous Posting" allows guest users who do not have accounts on your site to both create topics as well as replies.',                                                             'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'The Fancy Editor brings the luxury of the Visual editor and HTML editor from the traditional WordPress dashboard into your theme.',                                                  'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Auto-embed will embed the media content from a URL directly into the replies. For example: links to Flickr and YouTube.',                                                            'bbpress' ) . '</li>' .
+						'</ul>' .
+					'</p>' .
+					'<p>' . esc_html__( 'You must click the Save Changes button at the bottom of the screen for new settings to take effect.', 'bbpress' ) . '</p>'
+	) );
+
+	// Theme Package
+	$current_screen->add_help_tab( array(
+		'id'      => 'theme_packages',
+		'title'   => esc_html__( 'Theme Packages', 'bbpress' ),
+		'content' => '<p>' . esc_html__( 'The "Theme Packages" section allows you to choose which theme package should be used.', 'bbpress' ) . '</p>' .
+					 '<p>' .
+						'<ul>' .
+							'<li>' . esc_html__( 'The "bbPress Default" package is installed by default.',      'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Some themes may choose to ignore this setting entirely.',     'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'Packages can be stacked to allow for intelligent fallbacks.', 'bbpress' ) . '</li>' .
+						'</ul>' .
+					'</p>'
+	) );
+
+	// Per Page
+	$current_screen->add_help_tab( array(
+		'id'      => 'per_page',
+		'title'   => esc_html__( 'Per Page', 'bbpress' ),
+		'content' => '<p>' . esc_html__( 'The "Per Page" section allows you to control the number of topics and replies appear on each page.',                                                    'bbpress' ) . '</p>' .
+						'<ul>' .
+							'<li>' . esc_html__( 'This is comparable to the WordPress "Reading Settings" page, where you can set the number of posts that should show on blog pages and in feeds.', 'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'These are broken up into two separate groups: one for what appears in your theme, another for RSS feeds.',                                        'bbpress' ) . '</li>' .
+						'</ul>' .
+					 '<p>'
+	) );
+
+	// Slugs
+	$current_screen->add_help_tab( array(
+		'id'      => 'slugs',
+		'title'   => esc_html__( 'Slugs', 'bbpress' ),
+		'content' => '<p>' . esc_html__( 'The "Slugs" section allows you to control the permalink structure for your forums.',                                                                                                            'bbpress' ) . '</p>' .
+						'<ul>' .
+							'<li>' . esc_html__( '"Archive Slugs" are used as the "root" for your forums and topics. If you combine these values with existing page slugs, bbPress will attempt to output the most correct title and content.', 'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( '"Single Slugs" are used as a prefix when viewing an individual forum, topic, reply, user, or view.',                                                                                          'bbpress' ) . '</li>' .
+							'<li>' . esc_html__( 'In the event of a slug collision with WordPress or BuddyPress, a warning will appear next to the problem slug(s).', 'bbpress' ) . '</li>' .
+						'</ul>' .
+					 '<p>'
+	) );
+
+	// Help Sidebar
+	$current_screen->set_help_sidebar(
+		'<p><strong>' . esc_html__( 'For more information:', 'bbpress' ) . '</strong></p>' .
+		'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+		'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+	);
+}
+
+/**
+ * Disable a settings field if it is forcibly set in the global options array.
+ *
+ * @since 2.2.0 bbPress (r4347)
+ *
+ * @param string $option_key
+ */
+function bbp_maybe_admin_setting_disabled( $option_key = '' ) {
+	disabled( isset( bbpress()->options[ $option_key ] ) );
+}
+
+/**
+ * Output settings API option
+ *
+ * @since 2.0.0 bbPress (r3203)
+ *
+ * @param string $option
+ * @param string $default
+ * @param bool $slug
+ */
+function bbp_form_option( $option, $default = '', $slug = false ) {
+	echo bbp_get_form_option( $option, $default, $slug );
+}
+	/**
+	 * Return settings API option
+	 *
+	 * @since 2.0.0 bbPress (r3203)
+	 *
+	 * @param string $option
+	 * @param string $default
+	 * @param bool   $is_slug
+	 *
+	 * @return mixed
+	 */
+	function bbp_get_form_option( $option, $default = '', $is_slug = false ) {
+
+		// Get the option and sanitize it
+		$value = get_option( $option, $default );
+
+		// Slug?
+		if ( true === $is_slug ) {
+			$value = esc_attr( apply_filters( 'editable_slug', $value ) );
+
+		// Not a slug
+		} else {
+			$value = esc_attr( $value );
+		}
+
+		// Fallback to default
+		if ( empty( $value ) ) {
+			$value = $default;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_option', $value, $option, $default, $is_slug );
+	}
+
+/**
+ * Used to check if a bbPress slug conflicts with an existing known slug.
+ *
+ * @since 2.0.0 bbPress (r3306)
+ *
+ * @param string $slug
+ * @param string $default
+ */
+function bbp_form_slug_conflict_check( $slug, $default ) {
+
+	// Only set the slugs once ver page load
+	static $the_core_slugs = array();
+
+	// Get the form value
+	$this_slug = bbp_get_form_option( $slug, $default, true );
+
+	if ( empty( $the_core_slugs ) ) {
+
+		// Slugs to check
+		$core_slugs = apply_filters( 'bbp_slug_conflict_check', array(
+
+			/** WordPress Core ****************************************************/
+
+			// Core Post Types
+			'post_base'       => array( 'name' => esc_html__( 'Posts',         'bbpress' ), 'default' => 'post',          'context' => 'WordPress' ),
+			'page_base'       => array( 'name' => esc_html__( 'Pages',         'bbpress' ), 'default' => 'page',          'context' => 'WordPress' ),
+			'revision_base'   => array( 'name' => esc_html__( 'Revisions',     'bbpress' ), 'default' => 'revision',      'context' => 'WordPress' ),
+			'attachment_base' => array( 'name' => esc_html__( 'Attachments',   'bbpress' ), 'default' => 'attachment',    'context' => 'WordPress' ),
+			'nav_menu_base'   => array( 'name' => esc_html__( 'Menus',         'bbpress' ), 'default' => 'nav_menu_item', 'context' => 'WordPress' ),
+
+			// Post Tags
+			'tag_base'        => array( 'name' => esc_html__( 'Tag base',      'bbpress' ), 'default' => 'tag',           'context' => 'WordPress' ),
+
+			// Post Categories
+			'category_base'   => array( 'name' => esc_html__( 'Category base', 'bbpress' ), 'default' => 'category',      'context' => 'WordPress' ),
+
+			/** bbPress Core ******************************************************/
+
+			// Forum archive slug
+			'_bbp_root_slug'          => array( 'name' => esc_html__( 'Forums base', 'bbpress' ), 'default' => 'forums', 'context' => 'bbPress' ),
+
+			// Topic archive slug
+			'_bbp_topic_archive_slug' => array( 'name' => esc_html__( 'Topics base', 'bbpress' ), 'default' => 'topics', 'context' => 'bbPress' ),
+
+			// Forum slug
+			'_bbp_forum_slug'         => array( 'name' => esc_html__( 'Forum slug',  'bbpress' ), 'default' => 'forum',  'context' => 'bbPress' ),
+
+			// Topic slug
+			'_bbp_topic_slug'         => array( 'name' => esc_html__( 'Topic slug',  'bbpress' ), 'default' => 'topic',  'context' => 'bbPress' ),
+
+			// Reply slug
+			'_bbp_reply_slug'         => array( 'name' => esc_html__( 'Reply slug',  'bbpress' ), 'default' => 'reply',  'context' => 'bbPress' ),
+
+			// Edit slug
+			'_bbp_edit_slug'          => array( 'name' => esc_html__( 'Edit slug',   'bbpress' ), 'default' => 'edit',   'context' => 'bbPress' ),
+
+			// User profile slug
+			'_bbp_user_slug'          => array( 'name' => esc_html__( 'User base',   'bbpress' ), 'default' => 'users',  'context' => 'bbPress' ),
+
+			// View slug
+			'_bbp_view_slug'          => array( 'name' => esc_html__( 'View base',   'bbpress' ), 'default' => 'view',   'context' => 'bbPress' ),
+
+			// Topic tag slug
+			'_bbp_topic_tag_slug'     => array( 'name' => esc_html__( 'Topic tag slug', 'bbpress' ), 'default' => 'topic-tag', 'context' => 'bbPress' ),
+		) );
+
+		/** BuddyPress Core *******************************************************/
+
+		if ( defined( 'BP_VERSION' ) ) {
+			$bp = buddypress();
+
+			// Loop through root slugs and check for conflict
+			if ( ! empty( $bp->pages ) ) {
+				foreach ( $bp->pages as $page => $page_data ) {
+					$page_base    = $page . '_base';
+					$page_title   = sprintf( esc_html__( '%s page', 'bbpress' ), $page_data->title );
+					$core_slugs[ $page_base ] = array(
+						'name'    => $page_title,
+						'default' => $page_data->slug,
+						'context' => 'BuddyPress'
+					);
+				}
+			}
+		}
+
+		// Set the static
+		$the_core_slugs = apply_filters( 'bbp_slug_conflict', $core_slugs );
+	}
+
+	// Loop through slugs to check
+	foreach ( $the_core_slugs as $key => $value ) {
+
+		// Get the slug
+		$slug_check = bbp_get_form_option( $key, $value['default'], true );
+
+		// Compare
+		if ( ( $slug !== $key ) && ( $slug_check === $this_slug ) ) : ?>
+
+			<span class="attention"><?php printf( esc_html__( 'Possible %1$s conflict: %2$s', 'bbpress' ), $value['context'], '<strong>' . $value['name'] . '</strong>' ); ?></span>
+
+		<?php endif;
+	}
+}
Index: /tags/2.6.6/src/includes/admin/styles/_admin.scss
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/_admin.scss	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/_admin.scss	(revision 7162)
@@ -0,0 +1,491 @@
+
+@import 'variables';
+@import 'mixins';
+
+
+body {
+	background: $body-background;
+}
+
+
+/* Links */
+
+a {
+	color: $link;
+
+	&:hover,
+	&:active,
+	&:focus {
+		color: $link-focus;
+	}
+}
+
+#media-upload a.del-link:hover,
+div.dashboard-widget-submit input:hover,
+.subsubsub a:hover,
+.subsubsub a.current:hover {
+	color: $link-focus;
+}
+
+
+/* Forms */
+
+input[type=checkbox]:checked:before {
+	color: $form-checked;
+}
+
+input[type=radio]:checked:before {
+	background: $form-checked;
+}
+
+.wp-core-ui input[type="reset"]:hover,
+.wp-core-ui input[type="reset"]:active {
+	color: $link-focus;
+}
+
+
+/* Core UI */
+
+.wp-core-ui {
+	.button-primary {
+		@include button( $button-color );
+	}
+
+	.wp-ui-primary {
+		color: $text-color;
+		background-color: $base-color;
+	}
+	.wp-ui-text-primary {
+		color: $base-color;
+	}
+
+	.wp-ui-highlight {
+		color: $menu-highlight-text;
+		background-color: $menu-highlight-background;
+	}
+	.wp-ui-text-highlight {
+		color: $menu-highlight-background;
+	}
+
+	.wp-ui-notification {
+		color: $menu-bubble-text;
+		background-color: $menu-bubble-background;
+	}
+	.wp-ui-text-notification {
+		color: $menu-bubble-background;
+	}
+
+	.wp-ui-text-icon {
+		color: $menu-icon;
+	}
+}
+
+
+/* List tables */
+
+.wrap .add-new-h2:hover, /* deprecated */
+.wrap .page-title-action:hover {
+	color: $menu-text;
+	background-color: $menu-background;
+}
+
+.view-switch a.current:before {
+	color: $menu-background;
+}
+
+.view-switch a:hover:before {
+	color: $menu-bubble-background;
+}
+
+
+/* Admin Menu */
+
+#adminmenuback,
+#adminmenuwrap,
+#adminmenu {
+	background: $menu-background;
+}
+
+#adminmenu a {
+	color: $menu-text;
+}
+
+#adminmenu div.wp-menu-image:before {
+	color: $menu-icon;
+}
+
+#adminmenu a:hover,
+#adminmenu li.menu-top:hover,
+#adminmenu li.opensub > a.menu-top,
+#adminmenu li > a.menu-top:focus {
+	color: $menu-highlight-text;
+	background-color: $menu-highlight-background;
+}
+
+#adminmenu li.menu-top:hover div.wp-menu-image:before,
+#adminmenu li.opensub > a.menu-top div.wp-menu-image:before {
+	color: $menu-highlight-icon;
+}
+
+
+/* Active tabs use a bottom border color that matches the page background color. */
+
+.about-wrap h2 .nav-tab-active,
+.nav-tab-active,
+.nav-tab-active:hover {
+	background-color: $body-background;
+	border-bottom-color: $body-background;
+}
+
+
+/* Admin Menu: submenu */
+
+#adminmenu .wp-submenu,
+#adminmenu .wp-has-current-submenu .wp-submenu,
+#adminmenu .wp-has-current-submenu.opensub .wp-submenu,
+.folded #adminmenu .wp-has-current-submenu .wp-submenu,
+#adminmenu a.wp-has-current-submenu:focus + .wp-submenu {
+	background: $menu-submenu-background;
+}
+
+#adminmenu li.wp-has-submenu.wp-not-current-submenu.opensub:hover:after {
+	border-right-color: $menu-submenu-background;
+}
+
+#adminmenu .wp-submenu .wp-submenu-head {
+	color: $menu-submenu-text;
+}
+
+#adminmenu .wp-submenu a,
+#adminmenu .wp-has-current-submenu .wp-submenu a,
+.folded #adminmenu .wp-has-current-submenu .wp-submenu a,
+#adminmenu a.wp-has-current-submenu:focus + .wp-submenu a,
+#adminmenu .wp-has-current-submenu.opensub .wp-submenu a {
+	color: $menu-submenu-text;
+
+	&:focus, &:hover {
+		color: $menu-submenu-focus-text;
+	}
+}
+
+
+/* Admin Menu: current */
+
+#adminmenu .wp-submenu li.current a,
+#adminmenu a.wp-has-current-submenu:focus + .wp-submenu li.current a,
+#adminmenu .wp-has-current-submenu.opensub .wp-submenu li.current a {
+	color: $menu-submenu-current-text;
+
+	&:hover, &:focus {
+		color: $menu-submenu-focus-text;
+	}
+}
+
+ul#adminmenu a.wp-has-current-submenu:after,
+ul#adminmenu > li.current > a.current:after {
+    border-right-color: $body-background;
+}
+
+#adminmenu li.current a.menu-top,
+#adminmenu li.wp-has-current-submenu a.wp-has-current-submenu,
+#adminmenu li.wp-has-current-submenu .wp-submenu .wp-submenu-head,
+.folded #adminmenu li.current.menu-top {
+	color: $menu-current-text;
+	background: $menu-current-background;
+}
+
+#adminmenu li.wp-has-current-submenu div.wp-menu-image:before,
+#adminmenu a.current:hover div.wp-menu-image:before,
+#adminmenu li.wp-has-current-submenu a:focus div.wp-menu-image:before,
+#adminmenu li.wp-has-current-submenu.opensub div.wp-menu-image:before,
+#adminmenu li:hover div.wp-menu-image:before,
+#adminmenu li a:focus div.wp-menu-image:before,
+#adminmenu li.opensub div.wp-menu-image:before,
+.ie8 #adminmenu li.opensub div.wp-menu-image:before {
+	color: $menu-current-icon;
+}
+
+
+/* Admin Menu: bubble */
+
+#adminmenu .awaiting-mod,
+#adminmenu .update-plugins {
+	color: $menu-bubble-text;
+	background: $menu-bubble-background;
+}
+
+#adminmenu li.current a .awaiting-mod,
+#adminmenu li a.wp-has-current-submenu .update-plugins,
+#adminmenu li:hover a .awaiting-mod,
+#adminmenu li.menu-top:hover > a .update-plugins {
+	color: $menu-bubble-current-text;
+	background: $menu-bubble-current-background;
+}
+
+
+/* Admin Menu: collapse button */
+
+#collapse-button {
+    color: $menu-collapse-text;
+}
+
+#collapse-button:hover,
+#collapse-button:focus {
+    color: $menu-submenu-focus-text;
+}
+
+/* Admin Bar */
+
+#wpadminbar {
+	color: $menu-text;
+	background: $menu-background;
+}
+
+#wpadminbar .ab-item,
+#wpadminbar a.ab-item,
+#wpadminbar > #wp-toolbar span.ab-label,
+#wpadminbar > #wp-toolbar span.noticon {
+	color: $menu-text;
+}
+
+#wpadminbar .ab-icon,
+#wpadminbar .ab-icon:before,
+#wpadminbar .ab-item:before,
+#wpadminbar .ab-item:after {
+	color: $menu-icon;
+}
+
+#wpadminbar:not(.mobile) .ab-top-menu > li:hover > .ab-item,
+#wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus,
+#wpadminbar.nojq .quicklinks .ab-top-menu > li > .ab-item:focus,
+#wpadminbar.nojs .ab-top-menu > li.menupop:hover > .ab-item,
+#wpadminbar .ab-top-menu > li.menupop.hover > .ab-item {
+	color: $menu-submenu-focus-text;
+	background: $menu-submenu-background;
+}
+
+#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label,
+#wpadminbar:not(.mobile) > #wp-toolbar li.hover span.ab-label,
+#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label {
+	color: $menu-submenu-focus-text;
+}
+
+#wpadminbar:not(.mobile) li:hover .ab-icon:before,
+#wpadminbar:not(.mobile) li:hover .ab-item:before,
+#wpadminbar:not(.mobile) li:hover .ab-item:after,
+#wpadminbar:not(.mobile) li:hover #adminbarsearch:before {
+	color: $menu-highlight-icon;
+}
+
+
+/* Admin Bar: submenu */
+
+#wpadminbar .menupop .ab-sub-wrapper {
+	background: $menu-submenu-background;
+}
+
+#wpadminbar .quicklinks .menupop ul.ab-sub-secondary,
+#wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu {
+	background: $menu-submenu-background-alt;
+}
+
+#wpadminbar .ab-submenu .ab-item,
+#wpadminbar .quicklinks .menupop ul li a,
+#wpadminbar .quicklinks .menupop.hover ul li a,
+#wpadminbar.nojs .quicklinks .menupop:hover ul li a {
+	color: $menu-submenu-text;
+}
+
+#wpadminbar .quicklinks li .blavatar,
+#wpadminbar .menupop .menupop > .ab-item:before {
+	color: $menu-icon;
+}
+
+#wpadminbar .quicklinks .menupop ul li a:hover,
+#wpadminbar .quicklinks .menupop ul li a:focus,
+#wpadminbar .quicklinks .menupop ul li a:hover strong,
+#wpadminbar .quicklinks .menupop ul li a:focus strong,
+#wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a,
+#wpadminbar .quicklinks .menupop.hover ul li a:hover,
+#wpadminbar .quicklinks .menupop.hover ul li a:focus,
+#wpadminbar.nojs .quicklinks .menupop:hover ul li a:hover,
+#wpadminbar.nojs .quicklinks .menupop:hover ul li a:focus,
+#wpadminbar li:hover .ab-icon:before,
+#wpadminbar li:hover .ab-item:before,
+#wpadminbar li a:focus .ab-icon:before,
+#wpadminbar li .ab-item:focus:before,
+#wpadminbar li .ab-item:focus .ab-icon:before,
+#wpadminbar li.hover .ab-icon:before,
+#wpadminbar li.hover .ab-item:before,
+#wpadminbar li:hover #adminbarsearch:before,
+#wpadminbar li #adminbarsearch.adminbar-focused:before {
+	color: $menu-submenu-focus-text;
+}
+
+#wpadminbar .quicklinks li a:hover .blavatar,
+#wpadminbar .quicklinks li a:focus .blavatar,
+#wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a .blavatar,
+#wpadminbar .menupop .menupop > .ab-item:hover:before,
+#wpadminbar.mobile .quicklinks .ab-icon:before,
+#wpadminbar.mobile .quicklinks .ab-item:before {
+	color: $menu-submenu-focus-text;
+}
+
+#wpadminbar.mobile .quicklinks .hover .ab-icon:before,
+#wpadminbar.mobile .quicklinks .hover .ab-item:before {
+	color: $menu-icon;
+}
+
+
+/* Admin Bar: search */
+
+#wpadminbar #adminbarsearch:before {
+	color: $menu-icon;
+}
+
+#wpadminbar > #wp-toolbar > #wp-admin-bar-top-secondary > #wp-admin-bar-search #adminbarsearch input.adminbar-input:focus {
+	color: $menu-text;
+	background: $adminbar-input-background;
+}
+
+/* Admin Bar: my account */
+
+#wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar > a img {
+	border-color: $adminbar-avatar-frame;
+	background-color: $adminbar-avatar-frame;
+}
+
+#wpadminbar #wp-admin-bar-user-info .display-name {
+	color: $menu-text;
+}
+
+#wpadminbar #wp-admin-bar-user-info a:hover .display-name {
+	color: $menu-submenu-focus-text;
+}
+
+#wpadminbar #wp-admin-bar-user-info .username {
+	color: $menu-submenu-text;
+}
+
+
+/* Pointers */
+
+.wp-pointer .wp-pointer-content h3 {
+	background-color: $highlight-color;
+	border-color: darken( $highlight-color, 5% );
+}
+
+.wp-pointer .wp-pointer-content h3:before {
+	color: $highlight-color;
+}
+
+.wp-pointer.wp-pointer-top .wp-pointer-arrow,
+.wp-pointer.wp-pointer-top .wp-pointer-arrow-inner,
+.wp-pointer.wp-pointer-undefined .wp-pointer-arrow,
+.wp-pointer.wp-pointer-undefined .wp-pointer-arrow-inner {
+	border-bottom-color: $highlight-color;
+}
+
+
+/* Media */
+
+.media-item .bar,
+.media-progress-bar div {
+	background-color: $highlight-color;
+}
+
+.details.attachment {
+	box-shadow:
+		inset 0 0 0 3px #fff,
+		inset 0 0 0 7px $highlight-color;
+}
+
+.attachment.details .check {
+	background-color: $highlight-color;
+	box-shadow: 0 0 0 1px #fff, 0 0 0 2px $highlight-color;
+}
+
+.media-selection .attachment.selection.details .thumbnail {
+	box-shadow: 0 0 0 1px #fff, 0 0 0 3px $highlight-color;
+}
+
+
+/* Themes */
+
+.theme-browser .theme.active .theme-name,
+.theme-browser .theme.add-new-theme a:hover:after,
+.theme-browser .theme.add-new-theme a:focus:after {
+	background: $highlight-color;
+}
+
+.theme-browser .theme.add-new-theme a:hover span:after,
+.theme-browser .theme.add-new-theme a:focus span:after {
+	color: $highlight-color;
+}
+
+.theme-section.current,
+.theme-filter.current {
+	border-bottom-color: $menu-background;
+}
+
+body.more-filters-opened .more-filters {
+	color: $menu-text;
+	background-color: $menu-background;
+}
+
+body.more-filters-opened .more-filters:before {
+	color: $menu-text;
+}
+
+body.more-filters-opened .more-filters:hover,
+body.more-filters-opened .more-filters:focus {
+	background-color: $menu-highlight-background;
+	color: $menu-highlight-text;
+}
+
+body.more-filters-opened .more-filters:hover:before,
+body.more-filters-opened .more-filters:focus:before {
+	color: $menu-highlight-text;
+}
+
+/* Widgets */
+
+.widgets-chooser li.widgets-chooser-selected {
+	background-color: $menu-highlight-background;
+	color: $menu-highlight-text;
+}
+
+.widgets-chooser li.widgets-chooser-selected:before,
+.widgets-chooser li.widgets-chooser-selected:focus:before {
+	color: $menu-highlight-text;
+}
+
+/* Responsive Component */
+
+div#wp-responsive-toggle a:before {
+	color: $menu-icon;
+}
+
+.wp-responsive-open div#wp-responsive-toggle a {
+	// ToDo: make inset border
+	border-color: transparent;
+	background: $menu-highlight-background;
+}
+
+.wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle a {
+	background: $menu-submenu-background;
+}
+
+.wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle .ab-icon:before {
+	color: $menu-icon;
+}
+
+/* TinyMCE */
+
+.mce-container.mce-menu .mce-menu-item:hover,
+.mce-container.mce-menu .mce-menu-item.mce-selected,
+.mce-container.mce-menu .mce-menu-item:focus,
+.mce-container.mce-menu .mce-menu-item-normal.mce-active,
+.mce-container.mce-menu .mce-menu-item-preview.mce-active {
+	background: $highlight-color;
+}
Index: /tags/2.6.6/src/includes/admin/styles/_mixins.scss
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/_mixins.scss	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/_mixins.scss	(revision 7162)
@@ -0,0 +1,54 @@
+/*
+ * Button mixin- creates 3d-ish button effect with correct
+ * highlights/shadows, based on a base color.
+ */
+@mixin button( $button-color, $text-color: #fff ) {
+	background: $button-color;
+	border-color: darken( $button-color, 10% ) darken( $button-color, 15% ) darken( $button-color, 15% );
+	color: $text-color;
+	box-shadow: 0 1px 0 darken( $button-color, 15% );
+	text-shadow: 0 -1px 1px darken( $button-color, 15% ),
+		1px 0 1px darken( $button-color, 15% ),
+		0 1px 1px darken( $button-color, 15% ),
+		-1px 0 1px darken( $button-color, 15% );
+
+	&:hover,
+	&:focus {
+		background: lighten( $button-color, 3% );
+		border-color: darken( $button-color, 15% );
+		color: $text-color;
+		box-shadow: 0 1px 0 darken( $button-color, 15% );
+	}
+
+	&:focus {
+		box-shadow: inset 0 1px 0 darken( $button-color, 10% ),
+					0 0 2px 1px #33b3db;
+	}
+
+	&:active,
+	&.active,
+	&.active:focus,
+	&.active:hover {
+		background: darken( $button-color, 10% );
+		border-color: darken( $button-color, 15% );
+	 	box-shadow: inset 0 2px 0 darken( $button-color, 15% );
+	}
+
+	&[disabled],
+	&:disabled,
+	&.button-primary-disabled,
+	&.disabled {
+		color: hsl( hue( $button-color ), 10%, 80% ) !important;
+		background: darken( $button-color, 8% ) !important;
+		border-color: darken( $button-color, 15% ) !important;
+		text-shadow: none !important;
+	}
+
+	&.button-hero {
+		box-shadow: 0 2px 0 darken( $button-color, 15% ) !important;
+		&:active {
+		 	box-shadow: inset 0 3px 0 darken( $button-color, 15% ) !important;
+		}
+	}
+
+}
Index: /tags/2.6.6/src/includes/admin/styles/_variables.scss
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/_variables.scss	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/_variables.scss	(revision 7162)
@@ -0,0 +1,58 @@
+// assign default value to all undefined variables
+
+
+// core variables
+
+$text-color: #fff !default;
+$base-color: #23282d !default;
+$icon-color: hsl( hue( $base-color ), 7%, 95% ) !default;
+$highlight-color: #0073aa !default;
+$notification-color: #d54e21 !default;
+
+
+// global
+
+$body-background: #f1f1f1 !default;
+
+$link: #0073aa !default;
+$link-focus: lighten( $link, 10% ) !default;
+
+$button-color: $highlight-color !default;
+$form-checked: $highlight-color !default;
+
+
+// admin menu & admin-bar
+
+$menu-text: $text-color !default;
+$menu-icon: $icon-color !default;
+$menu-background: $base-color !default;
+
+$menu-highlight-text: $text-color !default;
+$menu-highlight-icon: $text-color !default;
+$menu-highlight-background: $highlight-color !default;
+
+$menu-current-text: $menu-highlight-text !default;
+$menu-current-icon: $menu-highlight-icon !default;
+$menu-current-background: $menu-highlight-background !default;
+
+$menu-submenu-text: mix( $base-color, $text-color, 30% ) !default;
+$menu-submenu-background: darken( $base-color, 7% ) !default;
+$menu-submenu-background-alt: desaturate( lighten( $menu-background, 7% ), 7% ) !default;
+
+$menu-submenu-focus-text: $highlight-color !default;
+$menu-submenu-current-text: $text-color !default;
+
+$menu-bubble-text: $text-color !default;
+$menu-bubble-background: $notification-color !default;
+$menu-bubble-current-text: $text-color !default;
+$menu-bubble-current-background: $menu-submenu-background !default;
+
+$menu-collapse-text: $menu-icon !default;
+$menu-collapse-icon: $menu-icon !default;
+$menu-collapse-focus-text: $text-color !default;
+$menu-collapse-focus-icon: $menu-highlight-icon !default;
+
+$adminbar-avatar-frame: lighten( $menu-background, 7% ) !default;
+$adminbar-input-background: lighten( $menu-background, 7% ) !default;
+
+$menu-customizer-text: mix( $base-color, $text-color, 40% ) !default;
Index: /tags/2.6.6/src/includes/admin/styles/evergreen/colors.scss
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/evergreen/colors.scss	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/evergreen/colors.scss	(revision 7162)
@@ -0,0 +1,5 @@
+$base-color: #446950;
+$highlight-color: #56b274;
+$notification-color: lighten(#2a8c4b, 10%);
+
+@import "../admin";
Index: /tags/2.6.6/src/includes/admin/styles/index.php
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/admin/styles/mint/colors.scss
===================================================================
--- /tags/2.6.6/src/includes/admin/styles/mint/colors.scss	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/styles/mint/colors.scss	(revision 7162)
@@ -0,0 +1,5 @@
+$base-color: darken(desaturate(#78cd95, 10%), 10%);
+$highlight-color: desaturate(#467656, 10%);
+$notification-color: desaturate(#2a8c4b, 10%);
+
+@import "../admin";
Index: /tags/2.6.6/src/includes/admin/tools.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools.php	(revision 7162)
@@ -0,0 +1,556 @@
+<?php
+
+/**
+ * bbPress Admin Tools Page
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output a bbPress specific tools box
+ *
+ * @since 2.6.0 bbPress (r6273)
+ */
+function bbp_admin_tools_box() {
+
+	// Bail if user cannot access tools page
+	if ( ! current_user_can( 'bbp_tools_page' ) ) {
+		return;
+	}
+
+	// Get the tools pages
+	$links = array();
+	$tools = bbp_get_tools_admin_pages(); ?>
+
+	<div class="card">
+		<h3 class="title"><?php esc_html_e( 'Forums', 'bbpress' ) ?></h3>
+		<p><?php esc_html_e( 'bbPress provides the following tools to help you manage your forums:', 'bbpress' ); ?></p>
+
+		<?php
+
+		// Loop through tools and create links
+		foreach ( $tools as $tool ) {
+
+			// Skip if user cannot see this page
+			if ( ! current_user_can( $tool['cap'] ) ) {
+				continue;
+			}
+
+			// Add link to array
+			$links[] = sprintf( '<a href="%s">%s</a>', esc_url( add_query_arg( array( 'page' => $tool['page'] ), admin_url( 'tools.php' ) ) ), $tool['name'] );
+		}
+
+		// Output links
+		echo '<p class="bbp-tools-links">' . implode( ' &middot; ', $links ) . '</p>';
+
+	?></div>
+
+<?php
+}
+
+/**
+ * Register an admin area repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $args
+ * @return
+ */
+function bbp_register_repair_tool( $args = array() ) {
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, array(
+		'id'          => '',
+		'type'        => '',
+		'title'       => '',
+		'description' => '',
+		'callback'    => '',
+		'priority'    => 0,
+		'overhead'    => 'low',
+		'version'     => '',
+		'components'  => array(),
+
+		// @todo
+		'success'     => esc_html__( 'The repair was completed successfully', 'bbpress' ),
+		'failure'     => esc_html__( 'The repair was not successful',         'bbpress' )
+	), 'register_repair_tool' );
+
+	// Bail if missing required values
+	if ( empty( $r['id'] ) || empty( $r['priority'] ) || empty( $r['title'] ) || empty( $r['callback'] ) ) {
+		return;
+	}
+
+	// Add tool to the registered tools array
+	bbp_admin()->tools[ $r['id'] ] = array(
+		'type'        => $r['type'],
+		'title'       => $r['title'],
+		'description' => $r['description'],
+		'priority'    => $r['priority'],
+		'callback'    => $r['callback'],
+		'overhead'    => $r['overhead'],
+		'components'  => $r['components'],
+		'version'     => $r['version'],
+
+		// @todo
+		'success'     => $r['success'],
+		'failure'     => $r['failure'],
+	);
+}
+
+/**
+ * Register the default repair tools
+ *
+ * @since 2.6.0 bbPress (r5885)
+ */
+function bbp_register_default_repair_tools() {
+
+	// Topic meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-topic-meta',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate parent topic for each reply', 'bbpress' ),
+		'description' => esc_html__( 'Run this if replies appear in the wrong topics.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_topic_meta',
+		'priority'    => 5,
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_reply_post_type() )
+	) );
+
+	// Forum meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-forum-meta',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate parent forum for each topic and reply', 'bbpress' ),
+		'description' => esc_html__( 'Run this if topics or replies appear in the wrong forums.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_forum_meta',
+		'priority'    => 10,
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Forum visibility
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-forum-visibility',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate private and hidden forums', 'bbpress' ),
+		'description' => esc_html__( 'Run this if non-public forums are publicly visible.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_forum_visibility',
+		'priority'    => 15,
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_forum_post_type() )
+	) );
+
+	// Sync all topics in all forums
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-all-topics-forums',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate last activity in each topic and forum', 'bbpress' ),
+		'description' => esc_html__( 'Run this if freshness appears incorrectly.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_freshness',
+		'priority'    => 20,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_forum_post_type(), bbp_get_topic_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Sync all sticky topics in all forums
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-all-topics-sticky',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate sticky relationship of each topic', 'bbpress' ),
+		'description' => esc_html__( 'Run this if sticky topics appear incorrectly.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_sticky',
+		'priority'    => 25,
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_topic_post_type() )
+	) );
+
+	// Sync all hierarchical reply positions
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-all-reply-positions',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate position of each reply in each topic', 'bbpress' ),
+		'description' => esc_html__( 'Run this if replies appear in the wrong order.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_reply_menu_order',
+		'priority'    => 30,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_reply_post_type() )
+	) );
+
+	// Sync all topic engagements for all users
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-topic-engagements',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recalculate engagements in each topic for each user', 'bbpress' ),
+		'description' => esc_html__( 'Run this if voices appear incorrectly.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_topic_voice_count',
+		'priority'    => 35,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Update closed topic counts
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-sync-closed-topics',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Repair closed topic statuses', 'bbpress' ),
+		'description' => esc_html__( 'Run this if closed topics appear incorrectly.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_closed_topics',
+		'priority'    => 40,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_topic_post_type() )
+	) );
+
+	// Count topics
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-forum-topics',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount topics in each forum', 'bbpress' ),
+		'description' => esc_html__( 'Run this if the number of topics in any forums are incorrect.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_forum_topic_count',
+		'priority'    => 45,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_forum_post_type(), bbp_get_topic_post_type() )
+	) );
+
+	// Count topic tags
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-topic-tags',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount topics in each topic-tag', 'bbpress' ),
+		'description' => esc_html__( 'Run this if the number of topics in any topic-tags are incorrect.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_topic_tag_count',
+		'priority'    => 50,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_topic_tag_tax_id() )
+	) );
+
+	// Count forum replies
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-forum-replies',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount replies in each forum', 'bbpress' ),
+		'description' => esc_html__( 'Run this if the number of replies in any forums are incorrect.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_forum_reply_count',
+		'priority'    => 55,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_forum_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Count non-published replies to each forum
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-forum-hidden-replies',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount pending, spammed, and trashed replies in each forum', 'bbpress' ),
+		'description' => esc_html__( 'Run this if non-public replies display incorrectly in forums.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_forum_hidden_reply_count',
+		'priority'    => 60,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_forum_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Count topic replies
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-topic-replies',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount replies in each topic', 'bbpress' ),
+		'description' => esc_html__( 'Run this if the number of replies in any topics are incorrect.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_topic_reply_count',
+		'priority'    => 65,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Count non-published replies to each topic
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-topic-hidden-replies',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount pending, spammed, and trashed replies in each topic', 'bbpress' ),
+		'description' => esc_html__( 'Run this if non-public replies display incorrectly in topics.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_topic_hidden_reply_count',
+		'priority'    => 70,
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() )
+	) );
+
+	// Recount topics for each user
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-topics',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount topics for each user', 'bbpress' ),
+		'description' => esc_html__( 'Run this to get fresh topic counts for all users.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_topic_count',
+		'priority'    => 75,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Recount topics for each user
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-replies',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Recount replies for each user', 'bbpress' ),
+		'description' => esc_html__( 'Run this to get fresh reply counts for all users.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_reply_count',
+		'priority'    => 80,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_reply_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Remove unpublished topics from user favorites
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-favorites',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Remove unpublished topics from user favorites', 'bbpress' ),
+		'description' => esc_html__( 'Run this to remove trashed or deleted topics from all user favorites.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_favorites',
+		'priority'    => 85,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Remove unpublished topics from user subscriptions
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-topic-subscriptions',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Remove unpublished topics from user subscriptions', 'bbpress' ),
+		'description' => esc_html__( 'Run this to remove trashed or deleted topics from all user subscriptions.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_topic_subscriptions',
+		'priority'    => 90,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_topic_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Remove unpublished forums from user subscriptions
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-forum-subscriptions',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Remove unpublished forums from user subscriptions', 'bbpress' ),
+		'description' => esc_html__( 'Run this to remove trashed or deleted forums from all user subscriptions.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_forum_subscriptions',
+		'priority'    => 95,
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_forum_post_type(), bbp_get_user_rewrite_id() )
+	) );
+
+	// Remove unpublished forums from user subscriptions
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-role-map',
+		'type'        => 'repair',
+		'title'       => esc_html__( 'Remap all users to default forum roles', 'bbpress' ),
+		'description' => esc_html__( 'Run this if users have issues accessing the forums.', 'bbpress' ),
+		'callback'    => 'bbp_admin_repair_user_roles',
+		'priority'    => 100,
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_user_rewrite_id() )
+	) );
+
+	// Migrate topic engagements to post-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-topic-engagements-move',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Upgrade user topic engagements', 'bbpress' ),
+		'description' => esc_html__( 'Copies engagements from user meta to topic meta.', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_user_engagements',
+		'priority'    => 105,
+		'version'     => '2.6.0',
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_engagements_rewrite_id() )
+	) );
+
+	// Migrate favorites from user-meta to post-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-favorites-move',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Upgrade user topic favorites', 'bbpress' ),
+		'description' => esc_html__( 'Copies favorites from user meta to topic meta.', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_user_favorites',
+		'priority'    => 110,
+		'version'     => '2.6.0',
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_favorites_rewrite_id() )
+	) );
+
+	// Migrate topic subscriptions from user-meta to post-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-topic-subscriptions-move',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Upgrade user topic subscriptions', 'bbpress' ),
+		'description' => esc_html__( 'Copies topic subscriptions from user meta to topic meta.', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_user_topic_subscriptions',
+		'priority'    => 115,
+		'version'     => '2.6.0',
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_subscriptions_rewrite_id() )
+	) );
+
+	// Migrate forum subscriptions from user-meta to post-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-forum-subscriptions-move',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Upgrade user forum subscriptions', 'bbpress' ),
+		'description' => esc_html__( 'Copies forum subscriptions from user meta to forum meta.', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_user_forum_subscriptions',
+		'priority'    => 120,
+		'version'     => '2.6.0',
+		'overhead'    => 'high',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_subscriptions_rewrite_id() )
+	) );
+
+	// Remove favorites from user-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-favorites-delete',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Remove favorites from user-meta', 'bbpress' ),
+		'description' => esc_html__( 'Run this to delete old data (after confirming successful favorites upgrade)', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_remove_favorites_from_usermeta',
+		'priority'    => 125,
+		'version'     => '2.6.1',
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_favorites_rewrite_id() )
+	) );
+
+	// Remove topic subscriptions from user-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-topic-subscriptions-delete',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Remove topic subscriptions from user-meta', 'bbpress' ),
+		'description' => esc_html__( 'Run this to delete old data (after confirming successful topic subscriptions upgrade)', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_remove_topic_subscriptions_from_usermeta',
+		'priority'    => 130,
+		'version'     => '2.6.1',
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_subscriptions_rewrite_id() )
+	) );
+
+	// Remove forum subscriptions from user-meta
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-user-forum-subscriptions-delete',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Remove forum subscriptions from user-meta', 'bbpress' ),
+		'description' => esc_html__( 'Run this to delete old data (after confirming successful forum subscriptions upgrade)', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_remove_forum_subscriptions_from_usermeta',
+		'priority'    => 135,
+		'version'     => '2.6.1',
+		'overhead'    => 'medium',
+		'components'  => array( bbp_get_user_rewrite_id(), bbp_get_user_subscriptions_rewrite_id() )
+	) );
+
+	// Sync all BuddyPress group forum relationships
+	bbp_register_repair_tool( array(
+		'id'          => 'bbp-group-forums',
+		'type'        => 'upgrade',
+		'title'       => esc_html__( 'Upgrade BuddyPress Group Forum relationships', 'bbpress' ),
+		'description' => esc_html__( 'Run this if you just upgraded BuddyPress Forums from Legacy.', 'bbpress' ),
+		'callback'    => 'bbp_admin_upgrade_group_forum_relationship',
+		'priority'    => 140,
+		'version'     => esc_html__( 'Any', 'bbpress' ),
+		'overhead'    => 'low',
+		'components'  => array( bbp_get_forum_post_type() )
+	) );
+}
+
+/**
+ * Output the tabs in the admin area
+ *
+ * @since 2.1.0 bbPress (r3872)
+ *
+ * @param string $active_tab Name of the tab that is active
+ */
+function bbp_tools_admin_tabs( $active_tab = '' ) {
+	echo bbp_get_tools_admin_tabs( $active_tab );
+}
+
+	/**
+	 * Output the tabs in the admin area
+	 *
+	 * @since 2.1.0 bbPress (r3872)
+	 *
+	 * @param string $active_tab Name of the tab that is active
+	 */
+	function bbp_get_tools_admin_tabs( $active_tab = '' ) {
+
+		// Declare local variables
+		$tabs_html    = '';
+		$idle_class   = 'nav-tab';
+		$active_class = 'nav-tab nav-tab-active';
+
+		// Setup core admin tabs
+		$tabs = bbp_get_tools_admin_pages();
+
+		// Loop through tabs and build navigation
+		foreach ( $tabs as $tab ) {
+
+			// Skip if user cannot visit page
+			if ( ! current_user_can( $tab['cap'] ) ) {
+				continue;
+			}
+
+			// Setup tab HTML
+			$is_current = (bool) ( $tab['page'] === $active_tab );
+			$tab_class  = $is_current ? $active_class : $idle_class;
+			$tab_url    = add_query_arg( array( 'page' => $tab['page'] ), admin_url( 'tools.php' ) );
+
+			// Tab name is not escaped - may contain HTML
+			$tabs_html .= '<a href="' . esc_url( $tab_url ) . '" class="' . esc_attr( $tab_class ) . '">' . $tab['name'] . '</a>';
+		}
+
+		// Output the tabs
+		return $tabs_html;
+	}
+
+/**
+ * Return possible tools pages
+ *
+ * @since 2.6.0 bbPress (r6273)
+ *
+ * @return array
+ */
+function bbp_get_tools_admin_pages() {
+
+	// Get tools URL one time & use in each tab
+	$tools_url = admin_url( 'tools.php' );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_tools_admin_tabs', array(
+		array(
+			'page' => 'bbp-repair',
+			'func' => 'bbp_admin_repair_page',
+			'cap'  => 'bbp_tools_repair_page',
+			'name' => bbp_maybe_append_pending_upgrade_count( esc_html__( 'Repair Forums', 'bbpress' ), 'repair' ),
+
+			// Deprecated 2.6.0
+			'href' => add_query_arg( array( 'page' => 'bbp-repair' ), $tools_url )
+		),
+		array(
+			'page' => 'bbp-upgrade',
+			'func' => 'bbp_admin_upgrade_page',
+			'cap'  => 'bbp_tools_upgrade_page',
+			'name' => bbp_maybe_append_pending_upgrade_count( esc_html__( 'Upgrade Forums', 'bbpress' ), 'upgrade' ),
+
+			// Deprecated 2.6.0
+			'href' => add_query_arg( array( 'page' => 'bbp-upgrade' ), $tools_url )
+		),
+		array(
+			'page' => 'bbp-converter',
+			'func' => 'bbp_converter_settings_page',
+			'cap'  => 'bbp_tools_import_page',
+			'name' => esc_html__( 'Import Forums', 'bbpress' ),
+
+			// Deprecated 2.6.0
+			'href' => add_query_arg( array( 'page' => 'bbp-converter' ), $tools_url )
+		),
+		array(
+			'page' => 'bbp-reset',
+			'func' => 'bbp_admin_reset_page',
+			'cap'  => 'bbp_tools_reset_page',
+			'name' => esc_html__( 'Reset Forums', 'bbpress' ),
+
+			// Deprecated 2.6.0
+			'href' => add_query_arg( array( 'page' => 'bbp-reset' ), $tools_url )
+		)
+	) );
+}
Index: /tags/2.6.6/src/includes/admin/tools/common.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/common.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/common.php	(revision 7162)
@@ -0,0 +1,933 @@
+<?php
+
+/**
+ * bbPress Admin Tools Common
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return the current admin repair tool page
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @return string
+ */
+function bbp_get_admin_repair_tool_page() {
+	return sanitize_key( $_GET['page'] );
+}
+
+/**
+ * Return the current admin repair tool page ID
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @return string
+ */
+function bbp_get_admin_repair_tool_page_id() {
+
+	// Get the page
+	$page = bbp_get_admin_repair_tool_page();
+
+	// Maybe trim prefix off of page
+	if ( ! empty( $page ) && ( 0 === strpos( $page, 'bbp-' ) ) ) {
+		$page = str_replace( 'bbp-', '', $page );
+	} else {
+		$page = '';
+	}
+
+	return $page;
+}
+
+/**
+ * Return a URL to the repair tool page
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function bbp_get_admin_repair_tool_page_url( $args = array() ) {
+
+	// Parse arguments
+	$r = wp_parse_args( $args, array(
+		'page' => bbp_get_admin_repair_tool_page()
+	) );
+
+	return add_query_arg( $r, admin_url( 'tools.php' ) );
+}
+
+/**
+ * Output the URL to run a specific repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param string $component
+ */
+function bbp_admin_repair_tool_run_url( $component = array() ) {
+	echo esc_url( bbp_get_admin_repair_tool_run_url( $component ) );
+}
+
+	/**
+	 * Return the URL to run a specific repair tool
+	 *
+	 * @since 2.6.0 bbPress (r5885)
+	 *
+	 * @param string $component
+	 */
+	function bbp_get_admin_repair_tool_run_url( $component = array() ) {
+
+		// Page
+		$page = ( 'repair' === $component['type'] )
+			? 'bbp-repair'
+			: 'bbp-upgrade';
+
+		// Arguments
+		$args = array(
+			'page'    => $page,
+			'action'  => 'run',
+			'checked' => array( $component['id'] )
+		);
+
+		// Url
+		$nonced = wp_nonce_url( bbp_get_admin_repair_tool_page_url( $args ), 'bbpress-do-counts' );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_admin_repair_tool_run_url', $nonced, $component );
+	}
+
+/**
+ * Assemble the admin notices
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @param string|WP_Error $message        A message to be displayed or {@link WP_Error}
+ * @param string          $class          Optional. A class to be added to the message div
+ * @param bool            $is_dismissible Optional. True to dismiss, false to persist
+ *
+ * @return string The message HTML
+ */
+function bbp_admin_tools_feedback( $message, $class = false, $is_dismissible = true ) {
+	return bbp_admin()->add_notice( $message, $class, $is_dismissible );
+}
+
+/**
+ * Handle the processing and feedback of the admin tools page
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ */
+function bbp_admin_repair_handler() {
+
+	// Bail if not an actionable request
+	if ( ! bbp_is_get_request() ) {
+		return;
+	}
+
+	// Get the current action or bail
+	if ( ! empty( $_GET['action'] ) ) {
+		$action = sanitize_key( $_GET['action'] );
+	} elseif ( ! empty( $_GET['action2'] ) ) {
+		$action = sanitize_key( $_GET['action2'] );
+	} else {
+		return;
+	}
+
+	// Bail if not running an action
+	if ( 'run' !== $action ) {
+		return;
+	}
+
+	check_admin_referer( 'bbpress-do-counts' );
+
+	// Parse list of checked repairs
+	$checked = ! empty( $_GET['checked'] )
+		? array_map( 'sanitize_key', $_GET['checked'] )
+		: array();
+
+	// Flush all caches before running tools
+	wp_cache_flush();
+
+	// Get the list
+	$list = bbp_get_admin_repair_tools();
+
+	// Stores messages
+	$messages = array();
+
+	// Run through checked repair tools
+	if ( count( $checked ) ) {
+		foreach ( $checked as $item_id ) {
+			if ( isset( $list[ $item_id ] ) && is_callable( $list[ $item_id ]['callback'] ) ) {
+				$messages[] = call_user_func( $list[ $item_id ]['callback'] );
+
+				// Remove from pending
+				bbp_remove_pending_upgrade( $item_id );
+			}
+		}
+	}
+
+	// Feedback
+	if ( count( $messages ) ) {
+		foreach ( $messages as $message ) {
+			bbp_admin_tools_feedback( $message[1] );
+		}
+	}
+
+	// Flush all caches after running tools
+	wp_cache_flush();
+}
+
+/**
+ * Get the array of available repair tools
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param string $type repair|upgrade The type of tools to get. Default empty for all tools.
+ * @return array
+ */
+function bbp_get_admin_repair_tools( $type = '' ) {
+
+	// Get tools array
+	$tools = ! empty( bbp_admin()->tools )
+		? bbp_admin()->tools
+		: array();
+
+	// Maybe limit to type (otherwise return all tools)
+	if ( ! empty( $type ) ) {
+		$tools = wp_list_filter( bbp_admin()->tools, array( 'type' => $type ) );
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tools', $tools, $type );
+}
+
+/**
+ * Return array of components from the array of registered tools
+ *
+ * @since 2.5.0 bbPress (r5885)
+ *
+ * @return array
+ */
+function bbp_get_admin_repair_tool_registered_components() {
+
+	// Default return value
+	$retval = array();
+
+	// Get tools
+	$tools  = bbp_get_admin_repair_tools( bbp_get_admin_repair_tool_page_id() );
+
+	// Loop through tools
+	if ( ! empty( $tools ) ) {
+		$plucked = wp_list_pluck( $tools, 'components' );
+
+		// Loop through components
+		if ( count( $plucked ) ) {
+			foreach ( $plucked as $components ) {
+				foreach ( $components as $component ) {
+
+					// Skip if already in array
+					if ( in_array( $component, $retval, true ) ) {
+						continue;
+					}
+
+					// Add component to the array
+					$retval[] = $component;
+				}
+			}
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tool_registered_components', $retval );
+}
+
+/**
+ * Output the repair list search form
+ *
+ * @since 2.6.0 bbPress (r5885)
+ */
+function bbp_admin_repair_list_search_form() {
+	?>
+
+	<p class="search-box">
+		<label class="screen-reader-text" for="bbp-repair-search-input"><?php esc_html_e( 'Search Tools:', 'bbpress' ); ?></label>
+		<input type="search" id="bbp-repair-search-input" name="s" value="<?php _admin_search_query(); ?>">
+		<input type="submit" id="search-submit" class="button" value="<?php esc_html_e( 'Search Tools', 'bbpress' ); ?>">
+	</p>
+
+	<?php
+}
+
+/**
+ * Output a select drop-down of components to filter by
+ *
+ * @since 2.5.0 bbPress (r5885)
+ */
+function bbp_admin_repair_list_components_filter() {
+
+	// Sanitize component value, if exists
+	$selected = ! empty( $_GET['components'] )
+		? sanitize_key( $_GET['components'] )
+		: '';
+
+	// Get registered components
+	$components = bbp_get_admin_repair_tool_registered_components();
+
+	// Bail if no components
+	if ( empty( $components ) ) {
+		return;
+	} ?>
+
+	<label class="screen-reader-text" for="components"><?php esc_html_e( 'Filter by Component', 'bbpress' ); ?></label>
+	<select name="components" id="components" class="postform">
+		<option value="" <?php selected( $selected, false ); ?>><?php esc_html_e( 'All Components', 'bbpress' ); ?></option>
+
+		<?php foreach ( $components as $component ) : ?>
+
+			<option class="level-0" value="<?php echo esc_attr( $component ); ?>" <?php selected( $selected, $component ); ?>><?php echo esc_html( bbp_admin_repair_tool_translate_component( $component ) ); ?></option>
+
+		<?php endforeach; ?>
+
+	</select>
+
+	<?php
+}
+
+/**
+ * Return array of versions from the array of registered tools
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @return array
+ */
+function bbp_get_admin_repair_tool_registered_versions() {
+
+	// Default return value
+	$retval = array();
+
+	// Get tools
+	$tools  = bbp_get_admin_repair_tools( bbp_get_admin_repair_tool_page_id() );
+
+	// Loop through tools
+	if ( ! empty( $tools ) ) {
+		$plucked = wp_list_pluck( $tools, 'version' );
+
+		// Loop through components
+		if ( count( $plucked ) ) {
+			foreach ( $plucked as $versions ) {
+
+				// Skip if empty
+				if ( empty( $versions ) ) {
+					continue;
+
+				// Cast to array if string
+				} elseif ( is_string( $versions ) ) {
+					$versions = (array) $versions;
+				}
+
+				// Loop through versions
+				foreach ( $versions as $version ) {
+
+					// Skip if already in array
+					if ( in_array( $version, $retval, true ) ) {
+						continue;
+					}
+
+					// Add component to the array
+					$retval[] = $version;
+				}
+			}
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tool_registered_versions', $retval );
+}
+
+/**
+ * Output a select drop-down of versions to filter by
+ *
+ * @since 2.5.0 bbPress (r6894)
+ */
+function bbp_admin_repair_list_versions_filter() {
+
+	// Sanitize component value, if exists
+	$selected = ! empty( $_GET['version'] )
+		? sanitize_text_field( $_GET['version'] )
+		: '';
+
+	// Get registered components
+	$versions = bbp_get_admin_repair_tool_registered_versions();
+
+	// Bail if no components
+	if ( empty( $versions ) ) {
+		return;
+	} ?>
+
+	<label class="screen-reader-text" for="version"><?php esc_html_e( 'Filter by Version', 'bbpress' ); ?></label>
+	<select name="version" id="version" class="postform">
+		<option value="" <?php selected( $selected, false ); ?>><?php esc_html_e( 'All Versions', 'bbpress' ); ?></option>
+
+		<?php foreach ( $versions as $version ) : ?>
+
+			<option class="level-0" value="<?php echo esc_attr( $version ); ?>" <?php selected( $selected, $version ); ?>><?php echo esc_html( bbp_admin_repair_tool_translate_version( $version ) ); ?></option>
+
+		<?php endforeach; ?>
+
+	</select>
+
+	<?php
+}
+
+/** Translations **************************************************************/
+
+/**
+ * Maybe translate a repair tool overhead name
+ *
+ * @since 2.6.0 bbPress (r6177)
+ *
+ * @param string $overhead
+ * @return string
+ */
+function bbp_admin_repair_tool_translate_overhead( $overhead = '' ) {
+
+	// Get the name of the component
+	switch ( $overhead ) {
+		case 'low' :
+			$name = esc_html__( 'Low', 'bbpress' );
+			break;
+		case 'medium' :
+			$name = esc_html__( 'Medium', 'bbpress' );
+			break;
+		case 'high' :
+			$name = esc_html__( 'High', 'bbpress' );
+			break;
+		default :
+			$name = ucwords( $overhead );
+			break;
+	}
+
+	return $name;
+}
+
+/**
+ * Maybe translate a repair tool component name
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param string $component
+ * @return string
+ */
+function bbp_admin_repair_tool_translate_component( $component = '' ) {
+
+	// Get the name of the component
+	switch ( $component ) {
+		case 'bbp_user' :
+			$name = esc_html__( 'Users', 'bbpress' );
+			break;
+		case bbp_get_forum_post_type() :
+			$name = esc_html__( 'Forums', 'bbpress' );
+			break;
+		case bbp_get_topic_post_type() :
+			$name = esc_html__( 'Topics', 'bbpress' );
+			break;
+		case bbp_get_reply_post_type() :
+			$name = esc_html__( 'Replies', 'bbpress' );
+			break;
+		case bbp_get_topic_tag_tax_id() :
+			$name = esc_html__( 'Topic Tags', 'bbpress' );
+			break;
+		case bbp_get_user_rewrite_id() :
+			$name = esc_html__( 'Users', 'bbpress' );
+			break;
+		case bbp_get_user_favorites_rewrite_id() :
+			$name = esc_html__( 'Favorites', 'bbpress' );
+			break;
+		case bbp_get_user_subscriptions_rewrite_id() :
+			$name = esc_html__( 'Subscriptions', 'bbpress' );
+			break;
+		case bbp_get_user_engagements_rewrite_id() :
+			$name = esc_html__( 'Engagements', 'bbpress' );
+			break;
+		default :
+			$name = ucwords( $component );
+			break;
+	}
+
+	return $name;
+}
+
+/**
+ * Maybe translate a repair tool overhead name
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @param string $version
+ * @return string
+ */
+function bbp_admin_repair_tool_translate_version( $version = '' ) {
+
+	// Get the version
+	switch ( $version ) {
+		case '2.5' :
+		case '2.5.0' :
+			$name = esc_html__( '2.5.0', 'bbpress' );
+			break;
+		case '2.6' :
+		case '2.6.0' :
+			$name = esc_html__( '2.6.0', 'bbpress' );
+			break;
+		default :
+			$name = sanitize_text_field( $version );
+			break;
+	}
+
+	return $name;
+}
+
+/** Lists *********************************************************************/
+
+/**
+ * Get the array of the repairs to show in a list table.
+ *
+ * Uses known filters to reduce the registered results down to the most finite
+ * set of tools.
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array Repair list of options
+ */
+function bbp_admin_repair_list( $type = 'repair' ) {
+
+	// Define empty array
+	$repair_list = array();
+
+	// Get the available tools
+	$list = bbp_get_admin_repair_tools( $type );
+
+	// Get pending upgrades
+	$pending = bbp_get_pending_upgrades();
+
+	// Search
+	$search = ! empty( $_GET['s'] )
+		? stripslashes( $_GET['s'] )
+		: '';
+
+	// Status
+	$status = ! empty( $_GET['status'] )
+		? sanitize_key( $_GET['status'] )
+		: '';
+
+	// Overhead
+	$overhead  = ! empty( $_GET['overhead'] )
+		? sanitize_key( $_GET['overhead'] )
+		: '';
+
+	// Component
+	$component = ! empty( $_GET['components'] )
+		? sanitize_key( $_GET['components'] )
+		: '';
+
+	// Version
+	$version = ! empty( $_GET['version'] )
+		? sanitize_text_field( $_GET['version'] )
+		: '';
+
+	// Orderby
+	$orderby = ! empty( $_GET['orderby'] )
+		? sanitize_key( $_GET['orderby'] )
+		: 'priority';
+
+	// Order
+	$order = ! empty( $_GET['order'] ) && in_array( strtolower( $_GET['order'] ), array( 'asc', 'desc' ), true )
+		? strtolower( $_GET['order'] )
+		: 'asc';
+
+	// Overhead filter
+	if ( ! empty( $overhead ) ) {
+		$list = wp_list_filter( $list, array( 'overhead' => $overhead ) );
+	}
+
+	if ( count( $list ) ) {
+
+		// Loop through and key by priority for sorting
+		foreach ( $list as $id => $tool ) {
+
+			// Status filter
+			if ( ! empty( $status ) && ( 'pending' === $status ) ) {
+				if ( ! in_array( $id, (array) $pending, true ) ) {
+					continue;
+				}
+			}
+
+			// Component filter
+			if ( ! empty( $component ) ) {
+				if ( ! in_array( $component, (array) $tool['components'], true ) ) {
+					continue;
+				}
+			}
+
+			// Version filter
+			if ( ! empty( $version ) ) {
+				if ( ! in_array( $version, (array) $tool['version'], true ) ) {
+					continue;
+				}
+			}
+
+			// Search
+			if ( ! empty( $search ) ) {
+				if ( ! strstr( strtolower( $tool['title'] ), strtolower( $search ) ) ) {
+					continue;
+				}
+			}
+
+			// Add to repair list
+			$repair_list[ $tool['priority'] ] = array(
+				'id'          => sanitize_key( $id ),
+				'type'        => $tool['type'],
+				'title'       => $tool['title'],
+				'priority'    => $tool['priority'],
+				'description' => $tool['description'],
+				'callback'    => $tool['callback'],
+				'overhead'    => $tool['overhead'],
+				'version'     => $tool['version'],
+				'components'  => $tool['components']
+			);
+		}
+	}
+
+	// Sort
+	$retval = wp_list_sort( $repair_list, $orderby, $order, true );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_repair_list', $retval );
+}
+
+/**
+ * Get filter links for components for a specific admin repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $item
+ * @return array
+ */
+function bbp_get_admin_repair_tool_components( $item = array() ) {
+
+	// Get the tools URL
+	$tools_url = bbp_get_admin_repair_tool_page_url();
+
+	// Define links array
+	$links      = array();
+	$components = ! empty( $item['components'] )
+		? (array) $item['components']
+		: array();
+
+	// Loop through tool components and build links
+	if ( count( $components ) ) {
+		foreach ( $components as $component ) {
+			$args       = array( 'components' => $component );
+			$filter_url = add_query_arg( $args, $tools_url );
+			$name       = bbp_admin_repair_tool_translate_component( $component );
+			$links[]    = '<a href="' . esc_url( $filter_url ) . '">' . esc_html( $name ) . '</a>';
+		}
+
+	// No components, so return a dash
+	} else {
+		$links[] = '&mdash;';
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tool_components', $links, $item );
+}
+
+/**
+ * Get filter links for versions for a specific admin repair tool
+ *
+ * @since 2.6.0 bbPress (r6894)
+ *
+ * @param array $item
+ * @return array
+ */
+function bbp_get_admin_repair_tool_version( $item = array() ) {
+
+	// Get the tools URL
+	$tools_url = bbp_get_admin_repair_tool_page_url();
+
+	// Define links array
+	$links    = array();
+	$versions = ! empty( $item['version'] )
+		? (array) $item['version']
+		: array();
+
+	// Loop through tool versions and build links
+	if ( count( $versions ) ) {
+		foreach ( $versions as $version ) {
+			$args       = array( 'version' => $version );
+			$filter_url = add_query_arg( $args, $tools_url );
+			$name       = bbp_admin_repair_tool_translate_version( $version );
+			$links[]    = '<a href="' . esc_url( $filter_url ) . '">' . esc_html( $name ) . '</a>';
+		}
+
+	// No versions, so return a dash
+	} else {
+		$links[] = '&mdash;';
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tool_version', $links, $item );
+}
+
+/**
+ * Get filter links for overhead for a specific admin repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $item
+ * @return array
+ */
+function bbp_get_admin_repair_tool_overhead( $item = array() ) {
+
+	// Get the tools URL
+	$tools_url = bbp_get_admin_repair_tool_page_url();
+
+	// Define links array
+	$links     = array();
+	$overheads = ! empty( $item['overhead'] )
+		? (array) $item['overhead']
+		: array();
+
+	// Loop through tool overhead and build links
+	if ( count( $overheads ) ) {
+		foreach ( $overheads as $overhead ) {
+			$args       = array( 'overhead' => $overhead );
+			$filter_url = add_query_arg( $args, $tools_url );
+			$name       = bbp_admin_repair_tool_translate_overhead( $overhead );
+			$links[]    = '<a href="' . esc_url( $filter_url ) . '">' . esc_html( $name ) . '</a>';
+		}
+
+	// No overhead, so return a single dash
+	} else {
+		$links[] = '&mdash;';
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_admin_repair_tool_overhead', $links, $item );
+}
+
+/** Overhead ******************************************************************/
+
+/**
+ * Output filter links for overheads for a specific admin repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $args
+ */
+function bbp_admin_repair_tool_overhead_filters( $args = array() ) {
+	echo bbp_get_admin_repair_tool_overhead_filters( $args );
+}
+
+/**
+ * Get filter links for overheads for a specific admin repair tool
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $args
+ * @return array
+ */
+function bbp_get_admin_repair_tool_overhead_filters( $args = array() ) {
+
+	// Parse args
+	$r = bbp_parse_args( $args, array(
+		'before'       => '<ul class="subsubsub">',
+		'after'        => '</ul>',
+		'link_before'  => '<li>',
+		'link_after'   => '</li>',
+		'count_before' => ' <span class="count">(',
+		'count_after'  => ')</span>',
+		'sep'          => ' | ',
+
+		// Retired, use 'sep' instead
+		'separator'    => false
+	), 'get_admin_repair_tool_overhead_filters' );
+
+	/**
+	 * Necessary for backwards compatibility
+	 * @see https://bbpress.trac.wordpress.org/ticket/2900
+	 */
+	if ( ! empty( $r['separator'] ) ) {
+		$r['sep'] = $r['separator'];
+	}
+
+	// Count the tools
+	$tools = bbp_get_admin_repair_tools( bbp_get_admin_repair_tool_page_id() );
+
+	// Get the tools URL
+	$tools_url = bbp_get_admin_repair_tool_page_url();
+
+	// Define arrays
+	$overheads = $links = array();
+
+	// Loop through tools and count overheads
+	if ( count( $tools ) ) {
+		foreach ( $tools as $tool ) {
+
+			// Get the overhead level
+			$overhead = $tool['overhead'];
+
+			// Set an empty count
+			if ( empty( $overheads[ $overhead ] ) ) {
+				$overheads[ $overhead ] = 0;
+			}
+
+			// Bump the overhead count
+			$overheads[ $overhead ]++;
+		}
+	}
+
+	// Get the current overhead, if any
+	$selected = ! empty( $_GET['overhead'] )
+		? sanitize_key( $_GET['overhead'] )
+		: '';
+
+	// Create the "All" link
+	$current = empty( $selected ) ? 'current' : '';
+	$links[] = $r['link_before'] . '<a href="' . esc_url( $tools_url ) . '" class="' . esc_attr( $current ) . '">' . sprintf( esc_html__( 'All %s', 'bbpress' ), $r['count_before'] . count( $tools ) . $r['count_after'] ) . '</a>' . $r['link_after'];
+
+	// Loop through overheads and created links
+	if ( count( $overheads ) ) {
+
+		// Sort
+		ksort( $overheads );
+
+		// Loop through overheads and build filter
+		foreach ( $overheads as $overhead => $count ) {
+
+			// Build the filter URL
+			$key        = sanitize_key( $overhead );
+			$args       = array( 'overhead' => $key );
+			$filter_url = add_query_arg( $args, $tools_url );
+
+			// Figure out separator and active class
+			$current  = ( $selected === $key )
+				? 'current'
+				: '';
+
+			// Counts to show
+			if ( ! empty( $count ) ) {
+				$overhead_count = $r['count_before'] . $count . $r['count_after'];
+			}
+
+			// Build the link
+			$links[] = $r['link_before'] . '<a href="' . esc_url( $filter_url ) . '" class="' . esc_attr( $current ) . '">' . bbp_admin_repair_tool_translate_overhead( $overhead ) . $overhead_count . '</a>' . $r['link_after'];
+		}
+	}
+
+	// Surround output with before & after strings
+	$output = $r['before'] . implode( $r['sep'], $links ) . $r['after'];
+
+	// Filter & return
+	return apply_filters( 'bbp_get_admin_repair_tool_overhead_filters', $output, $r, $args );
+}
+
+/** Pending ******************************************************************/
+
+/**
+ * Output filter links for statuses
+ *
+ * @since 2.6.0 bbPress (r6925)
+ *
+ * @param array $args
+ */
+function bbp_admin_repair_tool_status_filters( $args = array() ) {
+	echo bbp_get_admin_repair_tool_status_filters( $args );
+}
+
+/**
+ * Get filter links for statuses
+ *
+ * @since 2.6.0 bbPress (r5885)
+ *
+ * @param array $args
+ * @return array
+ */
+function bbp_get_admin_repair_tool_status_filters( $args = array() ) {
+
+	// Parse args
+	$r = bbp_parse_args( $args, array(
+		'before'       => '<ul class="subsubsub">',
+		'after'        => '</ul>',
+		'link_before'  => '<li>',
+		'link_after'   => '</li>',
+		'count_before' => ' <span class="count">(',
+		'count_after'  => ')</span>',
+		'sep'          => ' | ',
+
+		// Retired, use 'sep' instead
+		'separator'    => false
+	), 'get_admin_repair_tool_status_filters' );
+
+	/**
+	 * Necessary for backwards compatibility
+	 * @see https://bbpress.trac.wordpress.org/ticket/2900
+	 */
+	if ( ! empty( $r['separator'] ) ) {
+		$r['sep'] = $r['separator'];
+	}
+
+	// Get the type of tool
+	$type = bbp_get_admin_repair_tool_page_id();
+
+	// Count the tools
+	$tools = bbp_get_admin_repair_tools( $type );
+
+	// Get the tools URL
+	$tools_url = bbp_get_admin_repair_tool_page_url();
+
+	// Get pending upgrades
+	$pending = bbp_get_pending_upgrades( $type );
+
+	// Get the current status, if any
+	$selected = ! empty( $_GET['status'] )
+		? sanitize_key( $_GET['status'] )
+		: '';
+
+	// Nothing is current?
+	$all_current = empty( $selected )
+		? 'current'
+		: '';
+
+	// Pending is current?
+	$pending_current = ( 'pending' === $selected )
+		? 'current'
+		: '';
+
+	// Sort
+	ksort( $pending );
+
+	// Build the filter URL
+	$filter_url = add_query_arg( array(
+		'status' => 'pending'
+	), $tools_url );
+
+	// Count HTML
+	$all_count     = $r['count_before'] . count( $tools   ) . $r['count_after'];
+	$pending_count = $r['count_before'] . count( $pending ) . $r['count_after'];
+
+	// Define links
+	$links = array(
+		$r['link_before'] . '<a href="' . esc_url( $tools_url  ) . '" class="' . esc_attr( $all_current     ) . '">' . sprintf( esc_html__( 'All %s',     'bbpress' ), $all_count     ) . '</a>' . $r['link_after'],
+		$r['link_before'] . '<a href="' . esc_url( $filter_url ) . '" class="' . esc_attr( $pending_current ) . '">' . sprintf( esc_html__( 'Pending %s', 'bbpress' ), $pending_count ) . '</a>' . $r['link_after']
+	);
+
+	// Surround output with before & after strings
+	$output = $r['before'] . implode( $r['sep'], $links ) . $r['after'];
+
+	// Filter & return
+	return apply_filters( 'bbp_get_admin_repair_tool_status_filters', $output, $r, $args );
+}
Index: /tags/2.6.6/src/includes/admin/tools/converter.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/converter.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/converter.php	(revision 7162)
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * bbPress Converter
+ *
+ * Based on the hard work of Adam Ellis
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return an array of available converters
+ *
+ * @since 2.6.0 bbPress (r6447)
+ *
+ * @return array
+ */
+function bbp_get_converters() {
+	static $files = array();
+
+	// Only hit the file system one time per page load
+	if ( empty( $files ) ) {
+
+		// Open the converter directory
+		$path   = bbp_setup_converter()->converters_dir;
+		$curdir = opendir( $path );
+
+		// Look for the converter file in the converters directory
+		if ( false !== $curdir ) {
+			while ( $file = readdir( $curdir ) ) {
+				if ( stristr( $file, '.php' ) && stristr( $file, 'index' ) === false ) {
+					$name = preg_replace( '/.php/', '', $file );
+					if ( 'Example' !== $name ) {
+						$files[ $name ] = $path . $file;
+					}
+				}
+			}
+		}
+
+		// Close the directory
+		closedir( $curdir );
+
+		// Sort keys alphabetically, ignoring upper/lower casing
+		if ( ! empty( $files ) ) {
+			natcasesort( $files );
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_converters', $files );
+}
+
+/**
+ * This is a function that is purposely written to look like a "new" statement.
+ * It is basically a dynamic loader that will load in the platform conversion
+ * of your choice.
+ *
+ * @since 2.0.0
+ *
+ * @param string $platform Name of valid platform class.
+ *
+ * @return mixed Object if converter exists, null if not
+ */
+function bbp_new_converter( $platform = '' ) {
+
+	// Default converter
+	$converter = null;
+
+	// Bail if no platform
+	if ( empty( $platform ) ) {
+		return $converter;
+	}
+
+	// Get the available converters
+	$converters = bbp_get_converters();
+
+	// Get the converter file form converters array
+	$converter_file = isset( $converters[ $platform ] )
+		? $converters[ $platform ]
+		: '';
+
+	// Try to create a new converter object
+	if ( ! empty( $converter_file ) ) {
+
+		// Try to include the converter
+		@include_once $converter_file;
+
+		// Try to instantiate the converter object
+		if ( class_exists( $platform ) ) {
+			$converter = new $platform();
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_new_converter', $converter, $platform );
+}
Index: /tags/2.6.6/src/includes/admin/tools/help.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/help.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/help.php	(revision 7162)
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * bbPress Admin Tools Help
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Contextual help for Repair Forums tools page
+ *
+ * @since 2.6.0 bbPress (r5314)
+ */
+function bbp_admin_tools_repair_help() {
+
+	$current_screen = get_current_screen();
+
+	// Bail if current screen could not be found
+	if ( empty( $current_screen ) ) {
+		return;
+	}
+
+	// Repair Forums
+	$current_screen->add_help_tab( array(
+		'id'      => 'repair_forums',
+		'title'   => __( 'Repair Forums', 'bbpress' ),
+		'content' => '<p>' . __( 'There is more detailed information available on the bbPress and BuddyPress codex for the following:', 'bbpress' ) . '</p>' .
+					 '<p>' .
+						'<ul>' .
+							'<li>' . __( 'BuddyPress Group Forums: <a href="https://codex.buddypress.org/getting-started/installing-group-and-sitewide-forums/">Installing Group and Sitewide Forums</a> and <a href="https://codex.buddypress.org/getting-started/guides/migrating-from-old-forums-to-bbpress-2/">Migrating from old forums to bbPress 2.2+</a>.', 'bbpress' ) . '</li>' .
+							'<li>' . __( 'bbPress roles: <a href="https://codex.bbpress.org/bbpress-user-roles-and-capabilities/" target="_blank">bbPress User Roles and Capabilities</a>',                                                                                                                                                                        'bbpress' ) . '</li>' .
+						'</ul>' .
+					'</p>' .
+					'<p>' . __( 'Also see <a href="https://codex.bbpress.org/repair-forums/">bbPress: Repair Forums</a>.', 'bbpress' ) . '</p>'
+	) );
+
+	// Help Sidebar
+	$current_screen->set_help_sidebar(
+		'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+		'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+		'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+	);
+}
+
+/**
+ * Contextual help for Reset Forums tools page
+ *
+ * @since 2.6.0 bbPress (r5314)
+ */
+function bbp_admin_tools_reset_help() {
+
+	$current_screen = get_current_screen();
+
+	// Bail if current screen could not be found
+	if ( empty( $current_screen ) ) {
+		return;
+	}
+
+	// Reset Forums
+	$current_screen->add_help_tab( array(
+		'id'      => 'reset_forums',
+		'title'   => __( 'Reset Forums', 'bbpress' ),
+		'content' => '<p>' . __( 'Also see <a href="https://codex.bbpress.org/reset-forums/">bbPress: Reset Forums</a>.', 'bbpress' ) . '</p>'
+	) );
+
+	// Help Sidebar
+	$current_screen->set_help_sidebar(
+		'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+		'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+		'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+	);
+}
+
+/**
+ * Contextual help for Import Forums tools page
+ *
+ * @since 2.6.0 bbPress (r5314)
+ */
+function bbp_admin_tools_converter_help() {
+
+	$current_screen = get_current_screen();
+
+	// Bail if current screen could not be found
+	if ( empty( $current_screen ) ) {
+		return;
+	}
+
+	// Overview
+	$current_screen->add_help_tab( array(
+		'id'      => 'overview',
+		'title'   => __( 'Overview', 'bbpress' ),
+		'content' => '<p>' . __( 'This screen provides access to all of the bbPress Import Forums settings and resources.',                                      'bbpress' ) . '</p>' .
+					 '<p>' . __( 'Please see the additional help tabs for more information on each individual section.',                                         'bbpress' ) . '</p>' .
+					 '<p>' . __( 'Also see the main article on the bbPress codex <a href="https://codex.bbpress.org/import-forums/">bbPress: Import Forums</a>.', 'bbpress' ) . '</p>'
+	) );
+
+	// Database Settings
+	$current_screen->add_help_tab( array(
+		'id'      => 'database_settings',
+		'title'   => __( 'Database Settings', 'bbpress' ),
+		'content' => '<p>' . __( 'In the Database Settings you have a number of options:', 'bbpress' ) . '</p>' .
+					 '<p>' .
+						'<ul>' .
+							'<li>' . __( 'The settings in this section refer to the database connection strings used by your old forum software. The best way to determine the exact settings you need is to copy them from your legacy forums configuration file or contact your web hosting provider.', 'bbpress' ) . '</li>' .
+						'</ul>' .
+					'</p>'
+	) );
+
+	// Importer Options
+	$current_screen->add_help_tab( array(
+		'id'      => 'importer_options',
+		'title'   => __( 'Importer Options', 'bbpress' ),
+		'content' => '<p>' . __( 'In the Options you have a number of options:', 'bbpress' ) . '</p>' .
+					 '<p>' .
+						'<ul>' .
+							'<li>' . __( 'Depending on your MySQL configuration you can tweak the "Rows Limit" and "Delay Time" that may help to improve the overall time it takes to perform a complete forum import.', 'bbpress' ) . '</li>' .
+							'<li>' . __( '"Convert Users" will import your legacy forum members as WordPress Users.',                                                                                                    'bbpress' ) . '</li>' .
+							'<li>' . __( '"Start Over" will start the importer fresh, if your import failed for any reason leaving this setting unchecked the importer will begin from where it left off.',              'bbpress' ) . '</li>' .
+							'<li>' . __( '"Purge Previous Import" will remove data imported from a failed import without removing your existing forum data.',                                                            'bbpress' ) . '</li>' .
+						'</ul>' .
+					'</p>'
+	) );
+
+	// Help Sidebar
+	$current_screen->set_help_sidebar(
+		'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+		'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+		'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+	);
+}
Index: /tags/2.6.6/src/includes/admin/tools/repair.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/repair.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/repair.php	(revision 7162)
@@ -0,0 +1,1306 @@
+<?php
+
+/**
+ * bbPress Admin Repair Page
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Admin repair page
+ *
+ * @since 2.0.0 bbPress (r2613) Converted from bbPress 1.2
+ * @since 2.6.0 bbPress (r5885) Upgraded to list-table UI
+ *
+ * @todo Use a real list table
+ *
+ */
+function bbp_admin_repair_page() {
+
+	// Get the registered repair tools
+	$tools = bbp_admin_repair_list();
+
+	// Orderby
+	$orderby = ! empty( $_GET['orderby'] )
+		? sanitize_key( $_GET['orderby'] )
+		: 'priority';
+
+	// Order
+	$order = ! empty( $_GET['order'] ) && in_array( strtolower( $_GET['order'] ), array( 'asc', 'desc' ), true )
+		? strtolower( $_GET['order'] )
+		: 'asc';
+
+	// New order
+	$new_order = ( 'desc' === $order )
+		? 'asc'
+		: 'desc'; ?>
+
+	<div class="wrap">
+		<h1 class="wp-heading-inline"><?php esc_html_e( 'Forum Tools', 'bbpress' ); ?></h1>
+		<hr class="wp-header-end">
+		<h2 class="nav-tab-wrapper"><?php bbp_tools_admin_tabs( 'bbp-repair' ); ?></h2>
+
+		<p><?php esc_html_e( 'bbPress keeps track of relationships between forums, topics, replies, topic-tags, favorites, subscriptions, and users. Occasionally these relationships become out of sync, most often after an import or migration. Use the tools below to manually recalculate these relationships.', 'bbpress' ); ?></p>
+		<p class="description"><?php esc_html_e( 'Some of these tools create substantial database overhead. Use caution when running more than 1 repair at a time.', 'bbpress' ); ?></p>
+
+		<?php bbp_admin_repair_tool_status_filters(); ?>
+
+		<form class="settings" method="get" action="">
+
+			<?php bbp_admin_repair_list_search_form(); ?>
+
+			<input type="hidden" name="page" value="bbp-repair" />
+			<?php wp_nonce_field( 'bbpress-do-counts' ); ?>
+
+			<div class="tablenav top">
+				<div class="alignleft actions bulkactions">
+					<label for="bulk-action-selector-top" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', 'bbpress' ); ?></label>
+					<select name="action" id="bulk-action-selector-top">
+						<option value="" selected="selected"><?php esc_html_e( 'Bulk Actions', 'bbpress' ); ?></option>
+						<option value="run" class="hide-if-no-js"><?php esc_html_e( 'Run', 'bbpress' ); ?></option>
+					</select>
+					<input type="submit" id="doaction" class="button action" value="<?php esc_attr_e( 'Apply', 'bbpress' ); ?>">
+				</div>
+				<div class="alignleft actions">
+
+					<?php bbp_admin_repair_list_components_filter(); ?>
+
+					<input type="submit" name="filter_action" id="components-submit" class="button" value="<?php esc_html_e( 'Filter', 'bbpress' ); ?>">
+				</div>
+				<br class="clear">
+			</div>
+			<table class="wp-list-table widefat striped posts">
+				<thead>
+					<tr>
+						<td id="cb" class="manage-column column-cb check-column">
+							<label class="screen-reader-text" for="cb-select-all-1">
+								<?php esc_html_e( 'Select All', 'bbpress' ); ?>
+							</label>
+							<input id="cb-select-all-1" type="checkbox">
+						</td>
+						<th scope="col" id="description" class="manage-column column-primary column-description sortable <?php echo ( 'priority' === $orderby ) ? esc_attr( $order ) : 'asc'; ?>">
+							<a href="<?php echo esc_url( bbp_get_admin_repair_tool_page_url( array(
+									'orderby' => 'priority',
+									'order'   => $new_order
+								) ) ); ?>"><span><?php esc_html_e( 'Description', 'bbpress' ); ?></span><span class="sorting-indicator"></span>
+							</a>
+						</th>
+						<th scope="col" id="components" class="manage-column column-components">
+							<span><?php esc_html_e( 'Components', 'bbpress' ); ?></span>
+						</th>
+						<th scope="col" id="overhead" class="manage-column column-overhead sortable <?php echo ( 'overhead' === $orderby ) ? esc_attr( $order ) : 'asc'; ?>">
+							<a href="<?php echo esc_url( bbp_get_admin_repair_tool_page_url( array(
+									'orderby' => 'overhead',
+									'order'   => $new_order
+								) ) ); ?>"><span><?php esc_html_e( 'Overhead', 'bbpress' ); ?></span><span class="sorting-indicator"></span>
+							</a>
+						</th>
+					</tr>
+				</thead>
+
+				<tbody id="the-list">
+
+					<?php if ( ! empty( $tools ) ) : ?>
+
+						<?php foreach ( $tools as $item ) : ?>
+
+							<tr id="bbp-repair-tools" class="inactive">
+								<th scope="row" class="check-column">
+									<label class="screen-reader-text" for="<?php echo esc_attr( str_replace( '_', '-', $item['id'] ) ); ?>"></label>
+									<input type="checkbox" name="checked[]" value="<?php echo esc_attr( $item['id'] ); ?>" id="<?php echo esc_attr( str_replace( '_', '-', $item['id'] ) ); ?>">
+								</th>
+								<td class="bbp-tool-title column-primary column-description" data-colname="<?php esc_html_e( 'Description', 'bbpress' ); ?>">
+									<strong><?php echo esc_html( $item['title'] ); ?></strong><?php
+
+										// Optional description
+										if ( ! empty( $item['description'] ) ) :
+											echo '<p class="description">' . esc_html( $item['description'] ) . '</p>';
+										endif;
+
+									?><div class="row-actions hide-if-no-js">
+										<span class="run">
+											<a href="<?php bbp_admin_repair_tool_run_url( $item ); ?>" aria-label="<?php printf( esc_html__( 'Run %s', 'bbpress' ), $item['title'] ); ?>" id="<?php echo esc_attr( $item['id'] ); ?>" ><?php esc_html_e( 'Run', 'bbpress' ); ?></a>
+										</span>
+									</div>
+									<button type="button" class="toggle-row">
+										<span class="screen-reader-text"><?php esc_html_e( 'Show more details', 'bbpress' ); ?></span>
+									</button>
+								</td>
+								<td class="column-components desc" data-colname="<?php esc_html_e( 'Components', 'bbpress' ); ?>">
+									<div class="bbp-tool-overhead">
+
+										<?php echo implode( ', ', bbp_get_admin_repair_tool_components( $item ) ); ?>
+
+									</div>
+								</td>
+								<td class="column-overhead desc" data-colname="<?php esc_html_e( 'Overhead', 'bbpress' ); ?>">
+									<div class="bbp-tool-overhead">
+
+										<?php echo implode( ', ', bbp_get_admin_repair_tool_overhead( $item ) ); ?>
+
+									</div>
+								</td>
+							</tr>
+
+						<?php endforeach; ?>
+
+					<?php else : ?>
+
+						<tr>
+							<td colspan="4">
+								<?php esc_html_e( 'No repair tools match this criteria.', 'bbpress' ); ?>
+							</td>
+						</tr>
+
+					<?php endif; ?>
+
+				</tbody>
+				<tfoot>
+					<tr>
+						<td class="manage-column column-cb check-column">
+							<label class="screen-reader-text" for="cb-select-all-2">
+								<?php esc_html_e( 'Select All', 'bbpress' ); ?>
+							</label>
+							<input id="cb-select-all-2" type="checkbox">
+						</td>
+						<th scope="col" class="manage-column column-primary column-description"><?php esc_html_e( 'Description', 'bbpress' ); ?></th>
+						<th scope="col" class="manage-column column-components"><?php esc_html_e( 'Components', 'bbpress' ); ?></th>
+						<th scope="col" class="manage-column column-overhead"><?php esc_html_e( 'Overhead', 'bbpress' ); ?></th>
+					</tr>
+				</tfoot>
+			</table>
+			<div class="tablenav bottom">
+				<div class="alignleft actions bulkactions">
+					<label for="bulk-action-selector-bottom" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', 'bbpress' ); ?></label>
+					<select name="action2" id="bulk-action-selector-bottom">
+						<option value="" selected="selected"><?php esc_html_e( 'Bulk Actions', 'bbpress' ); ?></option>
+						<option value="run" class="hide-if-no-js"><?php esc_html_e( 'Run', 'bbpress' ); ?></option>
+					</select>
+					<input type="submit" id="doaction2" class="button action" value="<?php esc_attr_e( 'Apply', 'bbpress' ); ?>">
+				</div>
+			</div>
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Recount topic replies
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_topic_reply_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of replies in each topic&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// Post types and status
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+	$pps = bbp_get_public_status_id();
+	$cps = bbp_get_closed_status_id();
+
+	// Delete the meta key _bbp_reply_count for each topic
+	$sql_delete = "DELETE `postmeta` FROM `{$bbp_db->postmeta}` AS `postmeta`
+						LEFT JOIN `{$bbp_db->posts}` AS `posts` ON `posts`.`ID` = `postmeta`.`post_id`
+						WHERE `posts`.`post_type` = '{$tpt}'
+						AND `postmeta`.`meta_key` = '_bbp_reply_count'";
+
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Recalculate the meta key _bbp_reply_count for each topic
+	$sql = "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`) (
+			SELECT `topics`.`ID` AS `post_id`, '_bbp_reply_count' AS `meta_key`, COUNT(`replies`.`ID`) As `meta_value`
+				FROM `{$bbp_db->posts}` AS `topics`
+					LEFT JOIN `{$bbp_db->posts}` as `replies`
+						ON  `replies`.`post_parent` = `topics`.`ID`
+						AND `replies`.`post_status` = '{$pps}'
+						AND `replies`.`post_type`   = '{$rpt}'
+				WHERE `topics`.`post_type` = '{$tpt}'
+					AND `topics`.`post_status` IN ( '{$pps}', '{$cps}' )
+				GROUP BY `topics`.`ID`)";
+
+	if ( is_wp_error( $bbp_db->query( $sql ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount topic voices
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_topic_voice_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of voices in each topic&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$sql_delete = "DELETE FROM {$bbp_db->postmeta} WHERE meta_key IN ('_bbp_voice_count', '_bbp_engagement')";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and status
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+	$pps = bbp_get_public_status_id();
+	$cps = bbp_get_closed_status_id();
+
+	$engagements_sql = $bbp_db->prepare( "INSERT INTO {$bbp_db->postmeta} (post_id, meta_key, meta_value) (
+		SELECT postmeta.meta_value, '_bbp_engagement', posts.post_author
+			FROM {$bbp_db->posts} AS posts
+			LEFT JOIN {$bbp_db->postmeta} AS postmeta
+				ON posts.ID = postmeta.post_id
+				AND postmeta.meta_key = '_bbp_topic_id'
+			WHERE posts.post_type IN (%s, %s)
+				AND posts.post_status IN (%s, %s)
+			GROUP BY postmeta.meta_value, posts.post_author)", $tpt, $rpt, $pps, $cps );
+
+	if ( is_wp_error( $bbp_db->query( $engagements_sql ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$voice_count_sql = "INSERT INTO {$bbp_db->postmeta} (post_id, meta_key, meta_value) (
+		SELECT post_id, '_bbp_voice_count', COUNT(DISTINCT meta_value)
+			FROM {$bbp_db->postmeta}
+			WHERE meta_key = '_bbp_engagement'
+			GROUP BY post_id)";
+
+	if ( is_wp_error( $bbp_db->query( $voice_count_sql ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount non-public replies per topic (pending/spammed/trashed)
+ *
+ * @since 2.0.0 bbPress (r2747)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_topic_hidden_reply_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of pending, spammed, and trashed replies in each topic&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$sql_delete = "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` = '_bbp_reply_count_hidden'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and status
+	$rpt = bbp_get_reply_post_type();
+	$sta = bbp_get_non_public_topic_statuses();
+
+	// SQL
+	$sql_status = "'" . implode( "','", $sta ) . "'";
+
+	$sql = "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`) (SELECT `post_parent`, '_bbp_reply_count_hidden', COUNT(`post_status`) as `meta_value` FROM `{$bbp_db->posts}` WHERE `post_type` = '{$rpt}' AND `post_status` IN ({$sql_status}) GROUP BY `post_parent`)";
+	if ( is_wp_error( $bbp_db->query( $sql ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount forum topics
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_forum_topic_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of topics in each forum&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$sql_delete = "DELETE FROM {$bbp_db->postmeta} WHERE meta_key IN ( '_bbp_topic_count', '_bbp_total_topic_count', '_bbp_topic_count_hidden' )";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$forums = get_posts( array( 'post_type' => bbp_get_forum_post_type(), 'numberposts' => -1 ) );
+	if ( ! empty( $forums ) ) {
+		foreach ( $forums as $forum ) {
+			bbp_update_forum_topic_count( $forum->ID );
+			bbp_update_forum_topic_count_hidden( $forum->ID );
+		}
+	} else {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount topic in each topic-tag
+ *
+ * @since 2.6.0 bbPress (r6256)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_topic_tag_count() {
+
+	// Define variables
+	$statement = esc_html__( 'Counting the number of topics in each topic-tag&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+	$tax_id    = bbp_get_topic_tag_tax_id();
+	$terms     = get_terms( $tax_id, array( 'hide_empty' => false ) );
+	$tt_ids    = wp_list_pluck( $terms, 'term_taxonomy_id' );
+	$ints      = array_map( 'intval', $tt_ids );
+	$taxonomy  = get_taxonomy( $tax_id );
+
+	// Bail if taxonomy does not exist
+	if ( empty( $taxonomy ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Custom callback
+	if ( ! empty( $taxonomy->update_count_callback ) ) {
+
+		// Bail if callback is not callable
+		if ( ! is_callable( $taxonomy->update_count_callback ) ) {
+			return array( 1, sprintf( $statement, $result ) );
+		}
+
+		call_user_func( $taxonomy->update_count_callback, $ints, $taxonomy );
+
+	// Generic callback fallback
+	} else {
+		_update_post_term_count( $ints, $taxonomy );
+	}
+
+	// Bust the cache
+	clean_term_cache( $ints, '', false );
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount forum replies
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_forum_reply_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of replies in each forum&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// Post type
+	$fpt = bbp_get_forum_post_type();
+
+	// Delete the meta keys _bbp_reply_count and _bbp_total_reply_count for each forum
+	$sql_delete = "DELETE `postmeta` FROM `{$bbp_db->postmeta}` AS `postmeta`
+						LEFT JOIN `{$bbp_db->posts}` AS `posts` ON `posts`.`ID` = `postmeta`.`post_id`
+						WHERE `posts`.`post_type` = '{$fpt}'
+						AND `postmeta`.`meta_key` = '_bbp_reply_count'
+						OR `postmeta`.`meta_key` = '_bbp_total_reply_count'";
+
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Recalculate the metas key _bbp_reply_count and _bbp_total_reply_count for each forum
+	$forums = get_posts( array( 'post_type' => bbp_get_forum_post_type(), 'numberposts' => -1 ) );
+	if ( ! empty( $forums ) ) {
+		foreach ( $forums as $forum ) {
+			bbp_update_forum_reply_count( $forum->ID );
+		}
+	} else {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount non-public forum replies
+ *
+ * @since 2.6.0 bbPress (r6922)
+ * @since 2.6.0 bbPress (r6932) Rename to match the topic reply recount function
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_forum_hidden_reply_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Counting the number of pending, spammed, and trashed replies in each forum&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// Post type
+	$fpt = bbp_get_forum_post_type();
+
+	// Delete the meta keys _bbp_reply_count and _bbp_total_reply_count for each forum
+	$sql_delete = "DELETE `postmeta` FROM `{$bbp_db->postmeta}` AS `postmeta`
+						LEFT JOIN `{$bbp_db->posts}` AS `posts` ON `posts`.`ID` = `postmeta`.`post_id`
+						WHERE `posts`.`post_type` = '{$fpt}'
+						AND `postmeta`.`meta_key` = '_bbp_reply_count_hidden'
+						OR `postmeta`.`meta_key` = '_bbp_total_reply_count_hidden'";
+
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Recalculate the metas key _bbp_reply_count and _bbp_total_reply_count for each forum
+	$forums = get_posts( array( 'post_type' => bbp_get_forum_post_type(), 'numberposts' => -1 ) );
+	if ( ! empty( $forums ) ) {
+		foreach ( $forums as $forum ) {
+			bbp_update_forum_reply_count_hidden( $forum->ID );
+		}
+	} else {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount topics by the users
+ *
+ * @since 2.1.0 bbPress (r3889)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_user_topic_count() {
+
+	// Define variables
+	$bbp_db      = bbp_db();
+	$statement   = esc_html__( 'Counting the number of topics each user has created&hellip; %s', 'bbpress' );
+	$result      = esc_html__( 'Failed!', 'bbpress' );
+
+	$sql_select  = "SELECT `post_author`, COUNT(DISTINCT `ID`) as `_count` FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_topic_post_type() . "' AND `post_status` = '" . bbp_get_public_status_id() . "' GROUP BY `post_author`";
+	$insert_rows = $bbp_db->get_results( $sql_select );
+
+	if ( is_wp_error( $insert_rows ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$key           = $bbp_db->prefix . '_bbp_topic_count';
+	$insert_values = array();
+	foreach ( $insert_rows as $insert_row ) {
+		$insert_values[] = "('{$insert_row->post_author}', '{$key}', '{$insert_row->_count}')";
+	}
+
+	if ( !count( $insert_values ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$sql_delete = "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	foreach ( array_chunk( $insert_values, 10000 ) as $chunk ) {
+		$chunk = "\n" . implode( ",\n", $chunk );
+		$sql_insert = "INSERT INTO `{$bbp_db->usermeta}` (`user_id`, `meta_key`, `meta_value`) VALUES {$chunk}";
+
+		if ( is_wp_error( $bbp_db->query( $sql_insert ) ) ) {
+			return array( 4, sprintf( $statement, $result ) );
+		}
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recount topic replied by the users
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_user_reply_count() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement   = esc_html__( 'Counting the number of topics to which each user has replied&hellip; %s', 'bbpress' );
+	$result      = esc_html__( 'Failed!', 'bbpress' );
+
+	$sql_select  = "SELECT `post_author`, COUNT(DISTINCT `ID`) as `_count` FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_reply_post_type() . "' AND `post_status` = '" . bbp_get_public_status_id() . "' GROUP BY `post_author`";
+	$insert_rows = $bbp_db->get_results( $sql_select );
+
+	if ( is_wp_error( $insert_rows ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$key           = $bbp_db->prefix . '_bbp_reply_count';
+	$insert_values = array();
+	foreach ( $insert_rows as $insert_row ) {
+		$insert_values[] = "('{$insert_row->post_author}', '{$key}', '{$insert_row->_count}')";
+	}
+
+	if ( !count( $insert_values ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$sql_delete = "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	foreach ( array_chunk( $insert_values, 10000 ) as $chunk ) {
+		$chunk = "\n" . implode( ",\n", $chunk );
+		$sql_insert = "INSERT INTO `{$bbp_db->usermeta}` (`user_id`, `meta_key`, `meta_value`) VALUES {$chunk}";
+
+		if ( is_wp_error( $bbp_db->query( $sql_insert ) ) ) {
+			return array( 4, sprintf( $statement, $result ) );
+		}
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Repair user favorites
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_user_favorites() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Removing unpublished topics from user favorites&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// Query for users with favorites
+	$key       = $bbp_db->prefix . '_bbp_favorites';
+	$users     = $bbp_db->get_results( "SELECT `user_id`, `meta_value` AS `favorites` FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'" );
+
+	if ( is_wp_error( $users ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$topics = $bbp_db->get_col( "SELECT `ID` FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_topic_post_type() . "' AND `post_status` = '" . bbp_get_public_status_id() . "'" );
+
+	if ( is_wp_error( $topics ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$values = array();
+	foreach ( $users as $user ) {
+		if ( empty( $user->favorites ) || ! is_string( $user->favorites ) ) {
+			continue;
+		}
+
+		$favorites = array_intersect( $topics, explode( ',', $user->favorites ) );
+		if ( empty( $favorites ) || ! is_array( $favorites ) ) {
+			continue;
+		}
+
+		$favorites_joined = implode( ',', $favorites );
+		$values[]         = "('{$user->user_id}', '{$key}', '{$favorites_joined}')";
+
+		// Cleanup
+		unset( $favorites, $favorites_joined );
+	}
+
+	if ( !count( $values ) ) {
+		$result = esc_html__( 'Nothing to remove!', 'bbpress' );
+		return array( 0, sprintf( $statement, $result ) );
+	}
+
+	$sql_delete = "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 4, sprintf( $statement, $result ) );
+	}
+
+	foreach ( array_chunk( $values, 10000 ) as $chunk ) {
+		$chunk = "\n" . implode( ",\n", $chunk );
+		$sql_insert = "INSERT INTO `{$bbp_db->usermeta}` (`user_id`, `meta_key`, `meta_value`) VALUES {$chunk}";
+		if ( is_wp_error( $bbp_db->query( $sql_insert ) ) ) {
+			return array( 5, sprintf( $statement, $result ) );
+		}
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Clean the user topic subscriptions
+ *
+ * @since 2.0.0 bbPress (r2668)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_user_topic_subscriptions() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Removing trashed topics from user subscriptions&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$key       = $bbp_db->prefix . '_bbp_subscriptions';
+	$users     = $bbp_db->get_results( "SELECT `user_id`, `meta_value` AS `subscriptions` FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'" );
+
+	if ( is_wp_error( $users ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$topics = $bbp_db->get_col( "SELECT `ID` FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_topic_post_type() . "' AND `post_status` = '" . bbp_get_public_status_id() . "'" );
+	if ( is_wp_error( $topics ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$values = array();
+	foreach ( $users as $user ) {
+		if ( empty( $user->subscriptions ) || ! is_string( $user->subscriptions ) ) {
+			continue;
+		}
+
+		$subscriptions = array_intersect( $topics, explode( ',', $user->subscriptions ) );
+		if ( empty( $subscriptions ) || ! is_array( $subscriptions ) ) {
+			continue;
+		}
+
+		$subscriptions_joined = implode( ',', $subscriptions );
+		$values[]             = "('{$user->user_id}', '{$key}', '{$subscriptions_joined}')";
+
+		// Cleanup
+		unset( $subscriptions, $subscriptions_joined );
+	}
+
+	if ( !count( $values ) ) {
+		$result = esc_html__( 'Nothing to remove!', 'bbpress' );
+		return array( 0, sprintf( $statement, $result ) );
+	}
+
+	$sql_delete = "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 4, sprintf( $statement, $result ) );
+	}
+
+	foreach ( array_chunk( $values, 10000 ) as $chunk ) {
+		$chunk = "\n" . implode( ",\n", $chunk );
+		$sql_insert = "INSERT INTO `{$bbp_db->usermeta}` (`user_id`, `meta_key`, `meta_value`) VALUES {$chunk}";
+		if ( is_wp_error( $bbp_db->query( $sql_insert ) ) ) {
+			return array( 5, sprintf( $statement, $result ) );
+		}
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Clean the user forum subscriptions
+ *
+ * @since 2.5.0 bbPress (r5155)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_user_forum_subscriptions() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Removing trashed forums from user subscriptions&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$key       = $bbp_db->prefix . '_bbp_forum_subscriptions';
+	$users     = $bbp_db->get_results( "SELECT `user_id`, `meta_value` AS `subscriptions` FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'" );
+
+	if ( is_wp_error( $users ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$forums = $bbp_db->get_col( "SELECT `ID` FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_forum_post_type() . "' AND `post_status` = '" . bbp_get_public_status_id() . "'" );
+	if ( is_wp_error( $forums ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	$values = array();
+	foreach ( $users as $user ) {
+		if ( empty( $user->subscriptions ) || ! is_string( $user->subscriptions ) ) {
+			continue;
+		}
+
+		$subscriptions = array_intersect( $forums, explode( ',', $user->subscriptions ) );
+		if ( empty( $subscriptions ) || ! is_array( $subscriptions ) ) {
+			continue;
+		}
+
+		$subscriptions_joined = implode( ',', $subscriptions );
+		$values[]             = "('{$user->user_id}', '{$key}', '{$subscriptions_joined}')";
+
+		// Cleanup
+		unset( $subscriptions, $subscriptions_joined );
+	}
+
+	if ( !count( $values ) ) {
+		$result = esc_html__( 'Nothing to remove!', 'bbpress' );
+		return array( 0, sprintf( $statement, $result ) );
+	}
+
+	$sql_delete = "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '{$key}'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 4, sprintf( $statement, $result ) );
+	}
+
+	foreach ( array_chunk( $values, 10000 ) as $chunk ) {
+		$chunk = "\n" . implode( ",\n", $chunk );
+		$sql_insert = "INSERT INTO `{$bbp_db->usermeta}` (`user_id`, `meta_key`, `meta_value`) VALUES {$chunk}";
+		if ( is_wp_error( $bbp_db->query( $sql_insert ) ) ) {
+			return array( 5, sprintf( $statement, $result ) );
+		}
+	}
+
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * This repair tool will map each user of the current site to their respective
+ * forums role. By default, Admins will be Key Masters, and every other role
+ * will be the default role defined in Settings > Forums (Participant).
+ *
+ * @since 2.2.0 bbPress (r4340)
+ */
+function bbp_admin_repair_user_roles() {
+
+	$statement    = esc_html__( 'Remapping forum role for each user on this site&hellip; %s', 'bbpress' );
+	$changed      = 0;
+	$role_map     = bbp_get_user_role_map();
+	$default_role = bbp_get_default_role();
+
+	// Bail if no role map exists
+	if ( empty( $role_map ) ) {
+		return array( 1, sprintf( $statement, esc_html__( 'Failed!', 'bbpress' ) ) );
+	}
+
+	// Get non-forum roles
+	$blog_roles = array_keys( bbp_get_blog_roles() );
+
+	// Iterate through each role...
+	foreach ( $blog_roles as $role ) {
+
+		// Reset the offset
+		$offset = 0;
+
+		// If no role map exists, give the default forum role (bbp-participant)
+		$new_role = isset( $role_map[ $role ] )
+			? $role_map[ $role ]
+			: $default_role;
+
+		// Get users of this site, limited to 1000
+		while ( $users = get_users( array(
+			'role'   => $role,
+			'fields' => 'ID',
+			'number' => 1000,
+			'offset' => $offset
+		) ) ) {
+
+			// Iterate through each user of $role and try to set it
+			foreach ( (array) $users as $user_id ) {
+				if ( bbp_set_user_role( $user_id, $new_role ) ) {
+					++$changed; // Keep a count to display at the end
+				}
+			}
+
+			// Bump the offset for the next query iteration
+			$offset = $offset + 1000;
+		}
+	}
+
+	// Reset the offset
+	$offset  = 0;
+	$bbp_db  = bbp_db();
+	$cap_key = $bbp_db->get_blog_prefix() . 'capabilities';
+
+	// Users without roles should be granted the default role, but not on multi-
+	// site installations where not all users get a role by default.
+	if ( ! is_multisite() ) {
+
+		// Get users with missing capabilities on this site, limited to 1000
+		while ( $users = get_users( array(
+			'meta_key'     => $cap_key,
+			'meta_compare' => 'NOT EXISTS',
+			'fields'       => 'ID',
+			'number'       => 1000,
+			'offset'       => $offset
+		) ) ) {
+
+			// Iterate through each user of $role and try to set it
+			foreach ( (array) $users as $user_id ) {
+				if ( bbp_set_user_role( $user_id, $default_role ) ) {
+					++$changed; // Keep a count to display at the end
+				}
+			}
+
+			// Bump the offset for the next query iteration
+			$offset = $offset + 1000;
+		}
+
+	// On multisite, we'll look for users with an empty capabilities array.
+	// These are users who basically have malformed caps, and we can fix that.
+	} else {
+
+		// Get users with empty capabilities on this site, limited to 1000
+		while ( $users = get_users( array(
+			'meta_key'   => $cap_key,
+			'meta_value' => 'a:0:{}',
+			'fields'     => 'ID',
+			'number'     => 1000,
+			'offset'     => $offset
+		) ) ) {
+
+			// Iterate through each user of $role and try to set it
+			foreach ( (array) $users as $user_id ) {
+				if ( bbp_set_user_role( $user_id, $default_role ) ) {
+					++$changed; // Keep a count to display at the end
+				}
+			}
+
+			// Bump the offset for the next query iteration
+			$offset = $offset + 1000;
+		}
+	}
+
+	$result = sprintf( esc_html__( 'Complete! %s users updated.', 'bbpress' ), bbp_number_format( $changed ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Repair the last post in every topic and forum
+ *
+ * @since 2.0.0 bbPress (r3040)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_freshness() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Recomputing latest post in every topic and forum&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// First, delete everything.
+	if ( is_wp_error( $bbp_db->query( "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` IN ( '_bbp_last_reply_id', '_bbp_last_topic_id', '_bbp_last_active_id', '_bbp_last_active_time' )" ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and status
+	$fpt = bbp_get_forum_post_type();
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+	$pps = bbp_get_public_status_id();
+
+	// Next, give all the topics with replies the ID their last reply.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `topic`.`ID`, '_bbp_last_reply_id', MAX( `reply`.`ID` )
+			FROM `{$bbp_db->posts}` AS `topic` INNER JOIN `{$bbp_db->posts}` AS `reply` ON `topic`.`ID` = `reply`.`post_parent`
+			WHERE `reply`.`post_status` = '{$pps}' AND `topic`.`post_type` = '{$tpt}' AND `reply`.`post_type` = '{$rpt}'
+			GROUP BY `topic`.`ID` )" ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	// For any remaining topics, give a reply ID of 0.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `ID`, '_bbp_last_reply_id', 0
+			FROM `{$bbp_db->posts}` AS `topic` LEFT JOIN `{$bbp_db->postmeta}` AS `reply`
+			ON `topic`.`ID` = `reply`.`post_id` AND `reply`.`meta_key` = '_bbp_last_reply_id'
+			WHERE `reply`.`meta_id` IS NULL AND `topic`.`post_type` = '{$tpt}' )" ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	// Now we give all the forums with topics the ID their last topic.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `forum`.`ID`, '_bbp_last_topic_id', MAX( `topic`.`ID` )
+			FROM `{$bbp_db->posts}` AS `forum` INNER JOIN `{$bbp_db->posts}` AS `topic` ON `forum`.`ID` = `topic`.`post_parent`
+			WHERE `topic`.`post_status` = '{$pps}' AND `forum`.`post_type` = '{$fpt}' AND `topic`.`post_type` = '{$tpt}'
+			GROUP BY `forum`.`ID` )" ) ) ) {
+		return array( 4, sprintf( $statement, $result ) );
+	}
+
+	// For any remaining forums, give a topic ID of 0.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `ID`, '_bbp_last_topic_id', 0
+			FROM `{$bbp_db->posts}` AS `forum` LEFT JOIN `{$bbp_db->postmeta}` AS `topic`
+			ON `forum`.`ID` = `topic`.`post_id` AND `topic`.`meta_key` = '_bbp_last_topic_id'
+			WHERE `topic`.`meta_id` IS NULL AND `forum`.`post_type` = '{$fpt}' )" ) ) ) {
+		return array( 5, sprintf( $statement, $result ) );
+	}
+
+	// After that, we give all the topics with replies the ID their last reply (again, this time for a different reason).
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `topic`.`ID`, '_bbp_last_active_id', MAX( `reply`.`ID` )
+			FROM `{$bbp_db->posts}` AS `topic` INNER JOIN `{$bbp_db->posts}` AS `reply` ON `topic`.`ID` = `reply`.`post_parent`
+			WHERE `reply`.`post_status` = '{$pps}' AND `topic`.`post_type` = '{$tpt}' AND `reply`.`post_type` = '{$rpt}'
+			GROUP BY `topic`.`ID` )" ) ) ) {
+		return array( 6, sprintf( $statement, $result ) );
+	}
+
+	// For any remaining topics, give a reply ID of themself.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `ID`, '_bbp_last_active_id', `ID`
+			FROM `{$bbp_db->posts}` AS `topic` LEFT JOIN `{$bbp_db->postmeta}` AS `reply`
+			ON `topic`.`ID` = `reply`.`post_id` AND `reply`.`meta_key` = '_bbp_last_active_id'
+			WHERE `reply`.`meta_id` IS NULL AND `topic`.`post_type` = '{$tpt}' )" ) ) ) {
+		return array( 7, sprintf( $statement, $result ) );
+	}
+
+	// Give topics with replies their last update time.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `topic`.`ID`, '_bbp_last_active_time', MAX( `reply`.`post_date` )
+			FROM `{$bbp_db->posts}` AS `topic` INNER JOIN `{$bbp_db->posts}` AS `reply` ON `topic`.`ID` = `reply`.`post_parent`
+			WHERE `reply`.`post_status` = '{$pps}' AND `topic`.`post_type` = '{$tpt}' AND `reply`.`post_type` = '{$rpt}'
+			GROUP BY `topic`.`ID` )" ) ) ) {
+		return array( 8, sprintf( $statement, $result ) );
+	}
+
+	// Give topics without replies their last update time.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `ID`, '_bbp_last_active_time', `post_date`
+			FROM `{$bbp_db->posts}` AS `topic` LEFT JOIN `{$bbp_db->postmeta}` AS `reply`
+			ON `topic`.`ID` = `reply`.`post_id` AND `reply`.`meta_key` = '_bbp_last_active_time'
+			WHERE `reply`.`meta_id` IS NULL AND `topic`.`post_type` = '{$tpt}' )" ) ) ) {
+		return array( 9, sprintf( $statement, $result ) );
+	}
+
+	// Forums need to know what their last active item is as well. Now it gets a bit more complex to do in the database.
+	$forums = $bbp_db->get_col( "SELECT `ID` FROM `{$bbp_db->posts}` WHERE `post_type` = '{$fpt}' and `post_status` != 'auto-draft'" );
+	if ( is_wp_error( $forums ) ) {
+		return array( 10, sprintf( $statement, $result ) );
+	}
+
+	// Loop through forums
+	foreach ( $forums as $forum_id ) {
+		if ( ! bbp_is_forum_category( $forum_id ) ) {
+			bbp_update_forum( array( 'forum_id' => $forum_id ) );
+		}
+	}
+
+	// Loop through categories when forums are done
+	foreach ( $forums as $forum_id ) {
+		if ( bbp_is_forum_category( $forum_id ) ) {
+			bbp_update_forum( array( 'forum_id' => $forum_id ) );
+		}
+	}
+
+	// Complete results
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Repair the relationship of sticky topics to the actual parent forum
+ *
+ * @since 2.3.0 bbPress (r4695)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_sticky() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Repairing the sticky topic to the parent forum relationships&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	$forums    = $bbp_db->get_col( "SELECT ID FROM `{$bbp_db->posts}` WHERE `post_type` = '" . bbp_get_forum_post_type() . "'" );
+
+	// Bail if no forums found
+	if ( empty( $forums ) || is_wp_error( $forums ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Loop through forums and get their sticky topics
+	foreach ( $forums as $forum ) {
+		$forum_stickies[ $forum ] = get_post_meta( $forum, '_bbp_sticky_topics', true );
+	}
+
+	// Cleanup
+	unset( $forums, $forum );
+
+	// Loop through each forum with sticky topics
+	foreach ( $forum_stickies as $forum_id => $stickies ) {
+
+		// Skip if no stickies
+		if ( empty( $stickies ) ) {
+			continue;
+		}
+
+		// Loop through each sticky topic
+		foreach ( $stickies as $id => $topic_id ) {
+
+			// If the topic is not a super sticky, and the forum ID does not
+			// match the topic's forum ID, unset the forum's sticky meta.
+			if ( ! bbp_is_topic_super_sticky( $topic_id ) && ( $forum_id !== bbp_get_topic_forum_id( $topic_id ) ) ) {
+				unset( $forum_stickies[ $forum_id ][ $id ] );
+			}
+		}
+
+		// Get sticky topic ID's, or use empty string
+		$stickers = ! empty( $forum_stickies[ $forum_id ] )
+			? array_values( $forum_stickies[ $forum_id ] )
+			: '';
+
+		// Update the forum's sticky topics meta
+		update_post_meta( $forum_id, '_bbp_sticky_topics', $stickers );
+	}
+
+	// Complete results
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Repair closed topics
+ *
+ * Closed topics that are missing the post-meta "_bbp_status" key value "publish"
+ * result in unexpected behavior, primarily this would have only occurred if you
+ * had imported forums from another forum package previous to bbPress v2.6,
+ * https://bbpress.trac.wordpress.org/ticket/2577
+ *
+ * @since 2.6.0 bbPress (r5668)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_closed_topics() {
+
+	// Define variables
+	$bbp_db        = bbp_db();
+	$statement     = esc_html__( 'Repairing closed topics&hellip; %s', 'bbpress' );
+	$result        = esc_html__( 'No closed topics to repair.',        'bbpress' );
+	$changed       = 0;
+
+	// Results
+	$query         = "SELECT ID FROM `{$bbp_db->posts}` WHERE `post_type` = %s AND `post_status` = %s";
+	$prepare       = $bbp_db->prepare( $query, bbp_get_topic_post_type(), bbp_get_closed_status_id() );
+	$closed_topics = $bbp_db->get_col( $prepare );
+
+	// Bail if no closed topics found
+	if ( empty( $closed_topics ) || is_wp_error( $closed_topics ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Loop through each closed topic
+	foreach ( $closed_topics as $closed_topic ) {
+
+		// Check if the closed topic already has a postmeta _bbp_status value
+		$topic_status = get_post_meta( $closed_topic, '_bbp_status', true );
+
+		// If we don't have a postmeta _bbp_status value
+		if ( empty( $topic_status ) ) {
+			update_post_meta( $closed_topic, '_bbp_status', 'publish' );
+			++$changed; // Keep a count to display at the end
+		}
+	}
+
+	// Cleanup
+	unset( $closed_topics, $closed_topic, $topic_status );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d closed topic repaired.', 'Complete! %d closed topics repaired.', $changed, 'bbpress' ), $changed );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Repair the private and hidden forums
+ *
+ * @since 2.2.0 bbPress (r4104)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_forum_visibility() {
+	$statement = esc_html__( 'Recalculating forum visibility&hellip; %s', 'bbpress' );
+
+	// Bail if queries returned errors
+	if ( ! bbp_repair_forum_visibility() ) {
+		return array( 2, sprintf( $statement, esc_html__( 'Failed!',   'bbpress' ) ) );
+
+	// Complete results
+	} else {
+		return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+	}
+}
+
+/**
+ * Repair the parent forum meta for each topic and reply
+ *
+ * @since 2.1.0 bbPress (r3876)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_forum_meta() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Recalculating the forum for each post&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// First, delete everything.
+	if ( is_wp_error( $bbp_db->query( "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` = '_bbp_forum_id'" ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and status
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+	$fmt = bbp_get_forum_post_type();
+
+	// Next, give all the topics their parent forum id.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `topic`.`ID`, '_bbp_forum_id', `topic`.`post_parent`
+			FROM `$bbp_db->posts`
+				AS `topic`
+			WHERE `topic`.`post_type` = '{$tpt}'
+			GROUP BY `topic`.`ID` )" ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	// Next, give all the forums their parent forum id.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `forum`.`ID`, '_bbp_forum_id', `forum`.`post_parent`
+			FROM `$bbp_db->posts`
+				AS `forum`
+			WHERE `forum`.`post_type` = '{$fmt}'
+			GROUP BY `forum`.`ID` )" ) ) ) {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	// Next, give all the replies their parent forum id.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `reply`.`ID`, '_bbp_forum_id', `topic`.`post_parent`
+			FROM `$bbp_db->posts`
+				AS `reply`
+			INNER JOIN `$bbp_db->posts`
+				AS `topic`
+				ON `reply`.`post_parent` = `topic`.`ID`
+			WHERE `topic`.`post_type` = '{$tpt}'
+				AND `reply`.`post_type` = '{$rpt}'
+			GROUP BY `reply`.`ID` )" ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	// Complete results
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Repair the topic for each post
+ *
+ * @since 2.1.0 bbPress (r3876)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_topic_meta() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Recalculating the topic for each post&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'Failed!', 'bbpress' );
+
+	// First, delete everything.
+	if ( is_wp_error( $bbp_db->query( "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` = '_bbp_topic_id'" ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and status
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+
+	// Next, give all the topics with replies the ID their last reply.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `topic`.`ID`, '_bbp_topic_id', `topic`.`ID`
+			FROM `$bbp_db->posts`
+				AS `topic`
+			WHERE `topic`.`post_type` = '{$tpt}'
+			GROUP BY `topic`.`ID` )" ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
+	// Next, give all the topics with replies the ID their last reply.
+	if ( is_wp_error( $bbp_db->query( "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`)
+			( SELECT `reply`.`ID`, '_bbp_topic_id', `topic`.`ID`
+			FROM `$bbp_db->posts`
+				AS `reply`
+			INNER JOIN `$bbp_db->posts`
+				AS `topic`
+				ON `reply`.`post_parent` = `topic`.`ID`
+			WHERE `topic`.`post_type` = '{$tpt}'
+				AND `reply`.`post_type` = '{$rpt}'
+			GROUP BY `reply`.`ID` )" ) ) ) {
+		return array( 4, sprintf( $statement, $result ) );
+	}
+
+	// Complete results
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
+
+/**
+ * Recalculate reply menu order
+ *
+ * @since 2.5.4 bbPress (r5367)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_repair_reply_menu_order() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Recalculating reply menu order&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No reply positions to recalculate.',         'bbpress' );
+
+	// Delete cases where `_bbp_reply_to` was accidentally set to itself
+	if ( is_wp_error( $bbp_db->query( "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` = '_bbp_reply_to' AND `post_id` = `meta_value`" ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post type
+	$rpt = bbp_get_reply_post_type();
+
+	// Get an array of reply id's to update the menu oder for each reply
+	$replies = $bbp_db->get_results( "SELECT `a`.`ID` FROM `{$bbp_db->posts}` AS `a`
+										INNER JOIN (
+											SELECT `menu_order`, `post_parent`
+											FROM `{$bbp_db->posts}`
+											GROUP BY `menu_order`, `post_parent`
+											HAVING COUNT( * ) >1
+										)`b`
+										ON `a`.`menu_order` = `b`.`menu_order`
+										AND `a`.`post_parent` = `b`.`post_parent`
+										WHERE `post_type` = '{$rpt}'", OBJECT_K );
+
+	// Bail if no replies returned
+	if ( empty( $replies ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Recalculate the menu order position for each reply
+	foreach ( $replies as $reply ) {
+		bbp_update_reply_position( $reply->ID );
+	}
+
+	// Cleanup
+	unset( $replies, $reply );
+
+	// Complete results
+	return array( 0, sprintf( $statement, esc_html__( 'Complete!', 'bbpress' ) ) );
+}
Index: /tags/2.6.6/src/includes/admin/tools/reset.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/reset.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/reset.php	(revision 7162)
@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * bbPress Admin Tools Reset
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Admin reset page
+ *
+ * @since 2.0.0 bbPress (r2613)
+ *
+ */
+function bbp_admin_reset_page() {
+?>
+
+	<div class="wrap">
+		<h1 class="wp-heading-inline"><?php esc_html_e( 'Forum Tools', 'bbpress' ); ?></h1>
+		<hr class="wp-header-end">
+		<h2 class="nav-tab-wrapper"><?php bbp_tools_admin_tabs( 'bbp-reset' ); ?></h2>
+		<p><?php esc_html_e( 'Revert your forums back to a brand new installation, as if bbPress were never installed. This process cannot be undone.', 'bbpress' ); ?></p>
+
+		<form class="settings" method="post" action="">
+			<table class="form-table">
+				<tbody>
+					<tr valign="top">
+						<th scope="row"><?php esc_html_e( 'The following data will be removed:', 'bbpress' ) ?></th>
+						<td>
+							<?php esc_html_e( 'All Forums',           'bbpress' ); ?><br />
+							<?php esc_html_e( 'All Topics',           'bbpress' ); ?><br />
+							<?php esc_html_e( 'All Replies',          'bbpress' ); ?><br />
+							<?php esc_html_e( 'All Topic Tags',       'bbpress' ); ?><br />
+							<?php esc_html_e( 'All Meta Data',        'bbpress' ); ?><br />
+							<?php esc_html_e( 'Forum Settings',       'bbpress' ); ?><br />
+							<?php esc_html_e( 'Forum Activity',       'bbpress' ); ?><br />
+							<?php esc_html_e( 'Forum User Roles',     'bbpress' ); ?><br />
+							<?php esc_html_e( 'Forum Moderators',     'bbpress' ); ?><br />
+							<?php esc_html_e( 'Importer Helper Data', 'bbpress' ); ?><br />
+						</td>
+					</tr>
+					<tr valign="top">
+						<th scope="row"><?php esc_html_e( 'Delete imported users?', 'bbpress' ); ?></th>
+						<td>
+							<fieldset>
+								<legend class="screen-reader-text"><span><?php esc_html_e( "Say it ain't so!", 'bbpress' ); ?></span></legend>
+								<label><input type="checkbox" class="checkbox" name="bbpress-delete-imported-users" id="bbpress-delete-imported-users" value="1" /> <?php esc_html_e( 'This option will delete all previously imported users, and cannot be undone.', 'bbpress' ); ?></label>
+								<p class="description"><?php esc_html_e( 'Proceeding without this checked removes the meta-data necessary to delete these users later.', 'bbpress' ); ?></p>
+							</fieldset>
+						</td>
+					</tr>
+					<tr valign="top">
+						<th scope="row"><?php esc_html_e( 'Do you really want to do this?', 'bbpress' ); ?></th>
+						<td>
+							<fieldset>
+								<legend class="screen-reader-text"><span><?php esc_html_e( "Say it ain't so!", 'bbpress' ); ?></span></legend>
+								<label><input type="checkbox" class="checkbox" name="bbpress-are-you-sure" id="bbpress-are-you-sure" value="1" /> <?php esc_html_e( 'This process cannot be undone.', 'bbpress' ); ?></label>
+								<p class="description"><?php esc_html_e( 'Backup your database before proceeding.', 'bbpress' ); ?></p>
+							</fieldset>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+
+			<fieldset class="submit">
+				<input class="button-primary" type="submit" name="submit" value="<?php esc_attr_e( 'Reset bbPress', 'bbpress' ); ?>" />
+				<?php wp_nonce_field( 'bbpress-reset' ); ?>
+			</fieldset>
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Handle a bbPress admin area reset request.
+ *
+ * @since 2.0.0 bbPress (r2613)
+ */
+function bbp_admin_reset_handler() {
+
+	// Bail if not resetting.
+	if ( ! bbp_is_post_request() || empty( $_POST['bbpress-are-you-sure'] ) ) {
+		return;
+	}
+
+	// Only keymasters can proceed.
+	if ( ! bbp_is_user_keymaster() ) {
+		return;
+	}
+
+	// Bail if not referred from resetter
+	check_admin_referer( 'bbpress-reset' );
+
+	// Reset all of bbPress
+	bbp_admin_reset_database();
+}
+
+/**
+ * Wrapper for determining admin reset query feedback presented to a user.
+ *
+ * @since 2.6.0 bbPress (r6758)
+ *
+ * @param array $args Array of query, message, and possible responses
+ *
+ * @return string
+ */
+function bbp_admin_reset_query_feedback( $args = array() ) {
+	static $defaults = null;
+
+	// Only set defaults one time to avoid hitting the GetText API repeatedly
+	if ( null === $defaults ) {
+		$defaults = array(
+			'query'     => '',
+			'message'   => esc_html__( 'Resetting&hellip;', 'bbpress' ),
+			'responses' => array(
+				'success' => esc_html__( 'Success!', 'bbpress' ),
+				'failure' => esc_html__( 'Failed!',  'bbpress' ),
+				'skipped' => esc_html__( 'Skipped.', 'bbpress' )
+			)
+		);
+	}
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, $defaults, 'admin_reset_query_feedback' );
+
+	// Success/Failure based on query error
+	if ( ! empty( $r['query'] ) ) {
+		$query  = bbp_db()->query( $r['query'] );
+		$result = ! is_wp_error( $query )
+			? $r['responses']['success']
+			: $r['responses']['failure'];
+
+	// Skip if empty
+	} else {
+		$result = $r['responses']['skipped'];
+	}
+
+	// Return feedback
+	return sprintf( $r['message'], $result );
+}
+
+/**
+ * Perform a bbPress database reset.
+ *
+ * @since 2.6.0 bbPress
+ */
+function bbp_admin_reset_database() {
+
+	// Define variables.
+	$messages = array();
+	$sql_meta = array();
+	$bbp_db   = bbp_db();
+
+	// Flush the whole cache; things are about to get ugly.
+	wp_cache_flush();
+
+	/** Posts *****************************************************************/
+
+	// Post types and status.
+	$fpt = bbp_get_forum_post_type();
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+
+	// Get post IDs
+	$sql_posts = $bbp_db->get_results( "SELECT `ID` FROM `{$bbp_db->posts}` WHERE `post_type` IN ('{$fpt}', '{$tpt}', '{$rpt}')", OBJECT_K );
+	if ( ! empty( $sql_posts ) ) {
+
+		// Meta data
+		foreach ( $sql_posts as $key => $value ) {
+			$sql_meta[] = $key;
+		}
+		$sql_meta = implode( "', '", $sql_meta );
+
+		// Delete posts
+		$messages[] = bbp_admin_reset_query_feedback( array(
+			'query'   => "DELETE FROM `{$bbp_db->posts}` WHERE `post_type` IN ('{$fpt}', '{$tpt}', '{$rpt}')",
+			'message' => esc_html__( 'Removing Forums, Topics, and Replies&hellip; %s', 'bbpress' )
+		) );
+
+		/** Post Meta *********************************************************/
+
+		if ( ! empty( $sql_posts ) ) {
+			$messages[] = bbp_admin_reset_query_feedback( array(
+				'query'   => "DELETE FROM `{$bbp_db->postmeta}` WHERE `post_id` IN ('{$sql_meta}')",
+				'message' => esc_html__( 'Removing Forum, Topic, and Reply Meta Data&hellip; %s', 'bbpress' )
+			) );
+		}
+
+		/** Post Revisions ****************************************************/
+
+		if ( ! empty( $sql_posts ) ) {
+			$messages[] = bbp_admin_reset_query_feedback( array(
+				'query'   => "DELETE FROM `{$bbp_db->posts}` WHERE `post_parent` IN ('{$sql_meta}') AND `post_type` = 'revision'",
+				'message' => esc_html__( 'Removing Revision Data&hellip; %s', 'bbpress' )
+			) );
+		}
+	}
+
+	/** Topic Tags ************************************************************/
+
+	$messages[] = bbp_admin_reset_query_feedback( array(
+		'query'   => "DELETE a,b,c FROM `{$bbp_db->terms}` AS a LEFT JOIN `{$bbp_db->term_taxonomy}` AS c ON a.term_id = c.term_id LEFT JOIN `{$bbp_db->term_relationships}` AS b ON b.term_taxonomy_id = c.term_taxonomy_id WHERE c.taxonomy = 'topic-tag'",
+		'message' => esc_html__( 'Deleting Topic Tags&hellip; %s', 'bbpress' )
+	) );
+
+	/** User ******************************************************************/
+
+	// First, if we're deleting previously imported users, delete them now
+	if ( ! empty( $_POST['bbpress-delete-imported-users'] ) ) {
+		$sql_users = $bbp_db->get_results( "SELECT `user_id` FROM `{$bbp_db->usermeta}` WHERE `meta_key` = '_bbp_old_user_id'", OBJECT_K );
+
+		if ( ! empty( $sql_users ) ) {
+			$sql_meta = array();
+			foreach ( $sql_users as $key => $value ) {
+				$sql_meta[] = $key;
+			}
+
+			// Users
+			$sql_meta   = implode( "', '", $sql_meta );
+			$messages[] = bbp_admin_reset_query_feedback( array(
+				'query'   => "DELETE FROM `{$bbp_db->users}` WHERE `ID` IN ('{$sql_meta}')",
+				'message' => esc_html__( 'Deleting Imported Users&hellip; %s', 'bbpress' )
+			) );
+
+			// User meta
+			$messages[] = bbp_admin_reset_query_feedback( array(
+				'query'   => "DELETE FROM `{$bbp_db->usermeta}` WHERE `user_id` IN ('{$sql_meta}')",
+				'message' => esc_html__( 'Deleting Imported User Meta&hellip; %s', 'bbpress' )
+			) );
+		}
+	}
+
+	// Next, if we still have users that were not imported delete that meta data
+	$messages[] = bbp_admin_reset_query_feedback( array(
+		'query'   => "DELETE FROM `{$bbp_db->usermeta}` WHERE `meta_key` LIKE '%%_bbp_%%'",
+		'message' => esc_html__( 'Deleting bbPress Specific User Meta&hellip; %s', 'bbpress' )
+	) );
+
+	/** Converter *************************************************************/
+
+	$table_name = $bbp_db->prefix . 'bbp_converter_translator';
+	if ( $bbp_db->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name ) {
+		$messages[] = bbp_admin_reset_query_feedback( array(
+			'query'   => "DROP TABLE {$table_name}",
+			'message' => esc_html__( 'Dropping Conversion Table&hellip; %s', 'bbpress' )
+		) );
+	}
+
+	/** Options ***************************************************************/
+
+	bbp_delete_options();
+	$messages[] = esc_html__( 'Deleting Settings&hellip; Success!', 'bbpress' );
+
+	/** Roles *****************************************************************/
+
+	bbp_remove_roles();
+	bbp_remove_caps();
+	$messages[] = esc_html__( 'Removing Roles and Capabilities&hellip; Success!', 'bbpress' );
+
+	/** Output ****************************************************************/
+
+	if ( count( $messages ) ) {
+		foreach ( $messages as $message ) {
+			bbp_admin_tools_feedback( $message );
+		}
+	}
+}
Index: /tags/2.6.6/src/includes/admin/tools/upgrade.php
===================================================================
--- /tags/2.6.6/src/includes/admin/tools/upgrade.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/tools/upgrade.php	(revision 7162)
@@ -0,0 +1,678 @@
+<?php
+
+/**
+ * bbPress Admin Upgrade Functions
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Admin repair page
+ *
+ * @since 2.6.0 bbPress (r6278)
+ *
+ */
+function bbp_admin_upgrade_page() {
+
+	// Get the registered upgrade tools
+	$tools = bbp_admin_repair_list( 'upgrade' );
+
+	// Orderby
+	$orderby = ! empty( $_GET['orderby'] )
+		? sanitize_key( $_GET['orderby'] )
+		: 'priority';
+
+	// Order
+	$order = ! empty( $_GET['order'] ) && in_array( strtolower( $_GET['order'] ), array( 'asc', 'desc' ), true )
+		? strtolower( $_GET['order'] )
+		: 'asc';
+
+	// New order
+	$new_order = ( 'desc' === $order )
+		? 'asc'
+		: 'desc'; ?>
+
+	<div class="wrap">
+		<h1 class="wp-heading-inline"><?php esc_html_e( 'Forum Tools', 'bbpress' ); ?></h1>
+		<hr class="wp-header-end">
+		<h2 class="nav-tab-wrapper"><?php bbp_tools_admin_tabs( 'bbp-upgrade' ); ?></h2>
+
+		<p><?php esc_html_e( 'As bbPress improves, occasionally database upgrades are required but some forums are too large to upgrade automatically. Use the tools below to manually run upgrade routines.', 'bbpress' ); ?></p>
+		<p class="description"><?php esc_html_e( 'Some of these tools create substantial database overhead. Use caution when running more than 1 upgrade at a time.', 'bbpress' ); ?></p>
+
+		<?php bbp_admin_repair_tool_status_filters(); ?>
+
+		<form class="settings" method="get" action="">
+
+			<?php bbp_admin_repair_list_search_form(); ?>
+
+			<input type="hidden" name="page" value="bbp-upgrade" />
+			<?php wp_nonce_field( 'bbpress-do-counts' ); ?>
+
+			<div class="tablenav top">
+				<div class="alignleft actions bulkactions">
+					<label for="bulk-action-selector-top" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', 'bbpress' ); ?></label>
+					<select name="action" id="bulk-action-selector-top">
+						<option value="" selected="selected"><?php esc_html_e( 'Bulk Actions', 'bbpress' ); ?></option>
+						<option value="run" class="hide-if-no-js"><?php esc_html_e( 'Run', 'bbpress' ); ?></option>
+					</select>
+					<input type="submit" id="doaction" class="button action" value="<?php esc_attr_e( 'Apply', 'bbpress' ); ?>">
+				</div>
+				<div class="alignleft actions">
+
+					<?php bbp_admin_repair_list_components_filter(); ?>
+
+					<?php bbp_admin_repair_list_versions_filter(); ?>
+
+					<input type="submit" name="filter_action" id="components-submit" class="button" value="<?php esc_html_e( 'Filter', 'bbpress' ); ?>">
+				</div>
+				<br class="clear">
+			</div>
+			<table class="wp-list-table widefat striped posts">
+				<thead>
+					<tr>
+						<td id="cb" class="manage-column column-cb check-column">
+							<label class="screen-reader-text" for="cb-select-all-1">
+								<?php esc_html_e( 'Select All', 'bbpress' ); ?>
+							</label>
+							<input id="cb-select-all-1" type="checkbox">
+						</td>
+						<th scope="col" id="description" class="manage-column column-primary column-description sortable <?php echo ( 'priority' === $orderby ) ? esc_attr( $order ) : 'asc'; ?>">
+							<a href="<?php echo esc_url( bbp_get_admin_repair_tool_page_url( array(
+									'orderby' => 'priority',
+									'order'   => $new_order
+								) ) ); ?>"><span><?php esc_html_e( 'Description', 'bbpress' ); ?></span><span class="sorting-indicator"></span>
+							</a>
+						</th>
+						<th scope="col" id="version" class="manage-column column-version sortable <?php echo ( 'version' === $orderby ) ? esc_attr( $order ) : 'asc'; ?>">
+							<a href="<?php echo esc_url( bbp_get_admin_repair_tool_page_url( array(
+									'orderby' => 'version',
+									'order'   => $new_order
+								) ) ); ?>"><span><?php esc_html_e( 'Version', 'bbpress' ); ?></span><span class="sorting-indicator"></span>
+							</a>
+						</th>
+						<th scope="col" id="components" class="manage-column column-components"><?php esc_html_e( 'Components', 'bbpress' ); ?></th>
+						<th scope="col" id="overhead" class="manage-column column-overhead sortable <?php echo ( 'overhead' === $orderby ) ? esc_attr( $order ) : 'asc'; ?>">
+							<a href="<?php echo esc_url( bbp_get_admin_repair_tool_page_url( array(
+									'orderby' => 'overhead',
+									'order'   => $new_order
+								) ) ); ?>"><span><?php esc_html_e( 'Overhead', 'bbpress' ); ?></span><span class="sorting-indicator"></span>
+							</a>
+						</th>
+					</tr>
+				</thead>
+
+				<tbody id="the-list">
+
+					<?php if ( ! empty( $tools ) ) : ?>
+
+						<?php foreach ( $tools as $item ) : ?>
+
+							<tr id="bbp-repair-tools" class="inactive">
+								<th scope="row" class="check-column">
+									<label class="screen-reader-text" for="<?php echo esc_attr( str_replace( '_', '-', $item['id'] ) ); ?>"></label>
+									<input type="checkbox" name="checked[]" value="<?php echo esc_attr( $item['id'] ); ?>" id="<?php echo esc_attr( str_replace( '_', '-', $item['id'] ) ); ?>">
+								</th>
+								<td class="bbp-tool-title column-primary column-description" data-colname="<?php esc_html_e( 'Description', 'bbpress' ); ?>">
+									<strong><?php echo esc_html( $item['title'] ); ?></strong><?php
+
+										// Optional description
+										if ( ! empty( $item['description'] ) ) :
+											echo '<p class="description">' . esc_html( $item['description'] ) . '</p>';
+										endif;
+
+									?><div class="row-actions hide-if-no-js">
+										<span class="run">
+											<a href="<?php bbp_admin_repair_tool_run_url( $item ); ?>" aria-label="<?php printf( esc_html__( 'Run %s', 'bbpress' ), $item['title'] ); ?>" id="<?php echo esc_attr( $item['id'] ); ?>" ><?php esc_html_e( 'Run', 'bbpress' ); ?></a>
+										</span>
+									</div>
+									<button type="button" class="toggle-row">
+										<span class="screen-reader-text"><?php esc_html_e( 'Show more details', 'bbpress' ); ?></span>
+									</button>
+								</td>
+								<td class="column-version desc" data-colname="<?php esc_html_e( 'Version', 'bbpress' ); ?>">
+									<div class="bbp-tool-version">
+
+										<?php echo implode( ', ', bbp_get_admin_repair_tool_version( $item ) ); ?>
+
+									</div>
+								</td>
+								<td class="column-components desc" data-colname="<?php esc_html_e( 'Components', 'bbpress' ); ?>">
+									<div class="bbp-tool-components">
+
+										<?php echo implode( ', ', bbp_get_admin_repair_tool_components( $item ) ); ?>
+
+									</div>
+								</td>
+								<td class="column-overhead desc" data-colname="<?php esc_html_e( 'Overhead', 'bbpress' ); ?>">
+									<div class="bbp-tool-overhead">
+
+										<?php echo implode( ', ', bbp_get_admin_repair_tool_overhead( $item ) ); ?>
+
+									</div>
+								</td>
+							</tr>
+
+						<?php endforeach; ?>
+
+					<?php else : ?>
+
+						<tr>
+							<td colspan="4">
+								<?php esc_html_e( 'No repair tools match this criteria.', 'bbpress' ); ?>
+							</td>
+						</tr>
+
+					<?php endif; ?>
+
+				</tbody>
+				<tfoot>
+					<tr>
+						<td class="manage-column column-cb check-column">
+							<label class="screen-reader-text" for="cb-select-all-2">
+								<?php esc_html_e( 'Select All', 'bbpress' ); ?>
+							</label>
+							<input id="cb-select-all-2" type="checkbox">
+						</td>
+						<th scope="col" class="manage-column column-primary column-description"><?php esc_html_e( 'Description', 'bbpress' ); ?></th>
+						<th scope="col" class="manage-column column-version"><?php esc_html_e( 'Version', 'bbpress' ); ?></th>
+						<th scope="col" class="manage-column column-components"><?php esc_html_e( 'Components', 'bbpress' ); ?></th>
+						<th scope="col" class="manage-column column-overhead"><?php esc_html_e( 'Overhead', 'bbpress' ); ?></th>
+					</tr>
+				</tfoot>
+			</table>
+			<div class="tablenav bottom">
+				<div class="alignleft actions bulkactions">
+					<label for="bulk-action-selector-bottom" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', 'bbpress' ); ?></label>
+					<select name="action2" id="bulk-action-selector-bottom">
+						<option value="" selected="selected"><?php esc_html_e( 'Bulk Actions', 'bbpress' ); ?></option>
+						<option value="run" class="hide-if-no-js"><?php esc_html_e( 'Run', 'bbpress' ); ?></option>
+					</select>
+					<input type="submit" id="doaction2" class="button action" value="<?php esc_attr_e( 'Apply', 'bbpress' ); ?>">
+				</div>
+			</div>
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Upgrade user engagements for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_user_engagements() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Upgrading user engagements&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No engagements to upgrade.',            'bbpress' );
+
+	// Delete previous engagements
+	$sql_delete = "DELETE FROM {$bbp_db->postmeta} WHERE meta_key = '_bbp_engagement'";
+	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Post types and statuses
+	$tpt = bbp_get_topic_post_type();
+	$rpt = bbp_get_reply_post_type();
+	$pps = bbp_get_public_status_id();
+	$cps = bbp_get_closed_status_id();
+	$sql = "INSERT INTO {$bbp_db->postmeta} (post_id, meta_key, meta_value) (
+			SELECT postmeta.meta_value, '_bbp_engagement', posts.post_author
+				FROM {$bbp_db->posts} AS posts
+				LEFT JOIN {$bbp_db->postmeta} AS postmeta
+					ON posts.ID = postmeta.post_id
+					AND postmeta.meta_key = '_bbp_topic_id'
+				WHERE posts.post_type IN (%s, %s)
+					AND posts.post_status IN (%s, %s)
+				GROUP BY postmeta.meta_value, posts.post_author)";
+
+	// Run the big query
+	$prepare     = $bbp_db->prepare( $sql, $tpt, $rpt, $pps, $cps );
+	$engagements = $bbp_db->query( $prepare );
+
+	// Bail if no closed topics found
+	if ( empty( $engagements ) || is_wp_error( $engagements ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d engagement upgraded.', 'Complete! %d engagements upgraded.', $engagements, 'bbpress' ), bbp_number_format( $engagements ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Upgrade group forum ID mappings after a bbPress 1.x to bbPress 2.x conversion
+ *
+ * Previously named: bbp_admin_repair_group_forum_relationships()
+ *
+ * @since 2.6.0 bbPress (r4395)
+ *
+ * @return If a wp_error() occurs and no converted forums are found
+ */
+function bbp_admin_upgrade_group_forum_relationships() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Upgrading BuddyPress group-forum relationships&hellip; %s', 'bbpress' );
+	$g_count   = 0;
+	$f_count   = 0;
+	$s_count   = 0;
+
+	// Copy the BuddyPress filter here, incase BuddyPress is not active
+	$prefix            = apply_filters( 'bp_core_get_table_prefix', $bbp_db->base_prefix );
+	$groups_table      = $prefix . 'bp_groups';
+	$groups_meta_table = $prefix . 'bp_groups_groupmeta';
+
+	// Get the converted forum IDs
+	$forum_ids = $bbp_db->query( "SELECT `forum`.`ID`, `forummeta`.`meta_value`
+								FROM `{$bbp_db->posts}` AS `forum`
+									LEFT JOIN `{$bbp_db->postmeta}` AS `forummeta`
+										ON `forum`.`ID` = `forummeta`.`post_id`
+										AND `forummeta`.`meta_key` = '_bbp_old_forum_id'
+								WHERE `forum`.`post_type` = '" . bbp_get_forum_post_type() . "'
+								GROUP BY `forum`.`ID`" );
+
+	// Bail if forum IDs returned an error
+	if ( is_wp_error( $forum_ids ) || empty( $bbp_db->last_result ) ) {
+		return array( 2, sprintf( $statement, esc_html__( 'Failed!', 'bbpress' ) ) );
+	}
+
+	// Stash the last results
+	$results = $bbp_db->last_result;
+
+	// Update each group forum
+	foreach ( $results as $group_forums ) {
+
+		// Only update if is a converted forum
+		if ( empty( $group_forums->meta_value ) ) {
+			continue;
+		}
+
+		// Attempt to update group meta
+		$updated = $bbp_db->query( "UPDATE `{$groups_meta_table}` SET `meta_value` = '{$group_forums->ID}' WHERE `meta_key` = 'forum_id' AND `meta_value` = '{$group_forums->meta_value}'" );
+
+		// Bump the count
+		if ( ! empty( $updated ) && ! is_wp_error( $updated ) ) {
+			++$g_count;
+		}
+
+		// Update group to forum relationship data
+		$group_id = (int) $bbp_db->get_var( "SELECT `group_id` FROM `{$groups_meta_table}` WHERE `meta_key` = 'forum_id' AND `meta_value` = '{$group_forums->ID}'" );
+		if ( ! empty( $group_id ) ) {
+
+			// Update the group to forum meta connection in forums
+			update_post_meta( $group_forums->ID, '_bbp_group_ids', array( $group_id ) );
+
+			// Get the group status
+			$group_status = $bbp_db->get_var( "SELECT `status` FROM `{$groups_table}` WHERE `id` = '{$group_id}'" );
+
+			// Sync up forum visibility based on group status
+			switch ( $group_status ) {
+
+				// Public groups have public forums
+				case 'public' :
+					bbp_publicize_forum( $group_forums->ID );
+
+					// Bump the count for output later
+					++$s_count;
+					break;
+
+				// Private/hidden groups have hidden forums
+				case 'private' :
+				case 'hidden'  :
+					bbp_hide_forum( $group_forums->ID );
+
+					// Bump the count for output later
+					++$s_count;
+					break;
+			}
+
+			// Bump the count for output later
+			++$f_count;
+		}
+	}
+
+	// Make some logical guesses at the old group root forum
+	if ( function_exists( 'bp_forums_parent_forum_id' ) ) {
+		$old_default_forum_id = bp_forums_parent_forum_id();
+	} elseif ( defined( 'BP_FORUMS_PARENT_FORUM_ID' ) ) {
+		$old_default_forum_id = (int) BP_FORUMS_PARENT_FORUM_ID;
+	} else {
+		$old_default_forum_id = 1;
+	}
+
+	// Try to get the group root forum
+	$posts = get_posts( array(
+		'post_type'   => bbp_get_forum_post_type(),
+		'meta_key'    => '_bbp_old_forum_id',
+		'meta_type'   => 'NUMERIC',
+		'meta_value'  => $old_default_forum_id,
+		'numberposts' => 1
+	) );
+
+	// Found the group root forum
+	if ( ! empty( $posts ) ) {
+
+		// Rename 'Default Forum'  since it's now visible in sitewide forums
+		if ( 'Default Forum' === $posts[0]->post_title ) {
+			wp_update_post( array(
+				'ID'         => $posts[0]->ID,
+				'post_title' => esc_html__( 'Group Forums', 'bbpress' ),
+				'post_name'  => esc_html__( 'group-forums', 'bbpress' ),
+			) );
+		}
+
+		// Update the group forums root metadata
+		update_option( '_bbp_group_forums_root_id', $posts[0]->ID );
+	}
+
+	// Remove old bbPress 1.1 roles (BuddyPress)
+	remove_role( 'member'    );
+	remove_role( 'inactive'  );
+	remove_role( 'blocked'   );
+	remove_role( 'moderator' );
+	remove_role( 'keymaster' );
+
+	// Complete results
+	$result = sprintf( esc_html__( 'Complete! %s groups updated; %s forums updated; %s forum statuses synced.', 'bbpress' ), bbp_number_format( $g_count ), bbp_number_format( $f_count ), bbp_number_format( $s_count ) );
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Upgrade user favorites for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6174)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_user_favorites() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Upgrading user favorites&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No favorites to upgrade.',            'bbpress' );
+	$total     = 0;
+	$old_key   = $bbp_db->prefix . '_bbp_favorites';
+	$new_key   = '_bbp_favorite';
+
+	// Results
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s";
+	$prepare   = $bbp_db->prepare( $query, $old_key );
+	$favs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no closed topics found
+	if ( empty( $favs ) || is_wp_error( $favs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Loop through each user's favorites
+	foreach ( $favs as $meta ) {
+
+		// Get post IDs
+		$post_ids = explode( ',', $meta->meta_value );
+
+		// Add user ID to all favorited posts
+		foreach ( $post_ids as $post_id ) {
+
+			// Skip if already exists
+			if ( $bbp_db->get_var( $bbp_db->prepare( "SELECT COUNT(*) FROM {$bbp_db->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_value = %s", $post_id, $new_key, $meta->user_id ) ) ) {
+				continue;
+			}
+
+			// Add the post meta
+			$added = add_post_meta( $post_id, $new_key, $meta->user_id, false );
+
+			// Bump counts if successfully added
+			if ( ! empty( $added ) ) {
+				++$total;
+			}
+		}
+	}
+
+	// Cleanup
+	unset( $favs, $added, $post_ids );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d favorite upgraded.', 'Complete! %d favorites upgraded.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Upgrade user topic subscriptions for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6174)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_user_topic_subscriptions() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Upgrading user topic subscriptions&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No topic subscriptions to upgrade.',            'bbpress' );
+	$total     = 0;
+	$old_key   = $bbp_db->prefix . '_bbp_subscriptions';
+	$new_key   = '_bbp_subscription';
+
+	// Results
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s ORDER BY user_id";
+	$prepare   = $bbp_db->prepare( $query, $old_key );
+	$subs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no topic subscriptions found
+	if ( empty( $subs ) || is_wp_error( $subs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Loop through each user's topic subscriptions
+	foreach ( $subs as $meta ) {
+
+		// Get post IDs
+		$post_ids = explode( ',', $meta->meta_value );
+
+		// Add user ID to all subscribed topics
+		foreach ( $post_ids as $post_id ) {
+
+			// Skip if already exists
+			if ( $bbp_db->get_var( $bbp_db->prepare( "SELECT COUNT(*) FROM {$bbp_db->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_value = %s", $post_id, $new_key, $meta->user_id ) ) ) {
+				continue;
+			}
+
+			// Add the post meta
+			$added = add_post_meta( $post_id, $new_key, $meta->user_id, false );
+
+			// Bump counts if successfully added
+			if ( ! empty( $added ) ) {
+				++$total;
+			}
+		}
+	}
+
+	// Cleanup
+	unset( $subs, $added, $post_ids );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d topic subscription upgraded.', 'Complete! %d topic subscriptions upgraded.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Upgrade user forum subscriptions for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6193)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_user_forum_subscriptions() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Upgrading user forum subscriptions&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No forum subscriptions to upgrade.',            'bbpress' );
+	$total     = 0;
+	$old_key   = $bbp_db->prefix . '_bbp_forum_subscriptions';
+	$new_key   = '_bbp_subscription';
+
+	// Results
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s ORDER BY user_id";
+	$prepare   = $bbp_db->prepare( $query, $old_key );
+	$subs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no forum subscriptions found
+	if ( empty( $subs ) || is_wp_error( $subs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Loop through each user's forum subscriptions
+	foreach ( $subs as $meta ) {
+
+		// Get post IDs
+		$post_ids = explode( ',', $meta->meta_value );
+
+		// Add user ID to all subscribed forums
+		foreach ( $post_ids as $post_id ) {
+
+			// Skip if already exists
+			if ( $bbp_db->get_var( $bbp_db->prepare( "SELECT COUNT(*) FROM {$bbp_db->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_value = %s", $post_id, $new_key, $meta->user_id ) ) ) {
+				continue;
+			}
+
+			// Add the post meta
+			$added = add_post_meta( $post_id, $new_key, $meta->user_id, false );
+
+			// Bump counts if successfully added
+			if ( ! empty( $added ) ) {
+				++$total;
+			}
+		}
+	}
+
+	// Cleanup
+	unset( $subs, $added, $post_ids );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d forum subscription upgraded.', 'Complete! %d forum subscriptions upgraded.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Remove favorites data from user meta for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6281)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_remove_favorites_from_usermeta() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Remove favorites from usermeta&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No favorites to remove.',                   'bbpress' );
+	$total     = 0;
+	$key       = $bbp_db->prefix . '_bbp_favorites';
+
+	// Results
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s ORDER BY user_id";
+	$prepare   = $bbp_db->prepare( $query, $key );
+	$favs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no favorites found
+	if ( empty( $favs ) || is_wp_error( $favs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Delete all user-meta with this key
+	delete_metadata( 'user', false, $key, false, true );
+	$total = count( $favs );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d favorite deleted.', 'Complete! %d favorites deleted.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Remove topic subscriptions data from user meta for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6281)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_remove_topic_subscriptions_from_usermeta() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Remove topic subscriptions from usermeta&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No topic subscriptions to remove.',                   'bbpress' );
+	$total     = 0;
+	$key       = $bbp_db->prefix . '_bbp_subscriptions';
+
+	// Results
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s ORDER BY user_id";
+	$prepare   = $bbp_db->prepare( $query, $key );
+	$subs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no forum favorites found
+	if ( empty( $subs ) || is_wp_error( $subs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Delete all user-meta with this key
+	delete_metadata( 'user', false, $key, false, true );
+	$total = count( $subs );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d topic subscription deleted.', 'Complete! %d topic subscriptions deleted.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Remove topic subscriptions data from user meta for bbPress 2.6 and higher
+ *
+ * @since 2.6.0 bbPress (r6281)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_admin_upgrade_remove_forum_subscriptions_from_usermeta() {
+
+	// Define variables
+	$bbp_db    = bbp_db();
+	$statement = esc_html__( 'Remove forum subscriptions from usermeta&hellip; %s', 'bbpress' );
+	$result    = esc_html__( 'No forum subscriptions to remove.',                   'bbpress' );
+	$total     = 0;
+	$key       = $bbp_db->prefix . '_bbp_forum_subscriptions';
+
+	// Query
+	$query     = "SELECT * FROM {$bbp_db->usermeta} WHERE meta_key = %s ORDER BY user_id";
+	$prepare   = $bbp_db->prepare( $query, $key );
+	$subs      = $bbp_db->get_results( $prepare );
+
+	// Bail if no forum favorites found
+	if ( empty( $subs ) || is_wp_error( $subs ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	// Delete all user-meta with this key
+	delete_metadata( 'user', false, $key, false, true );
+	$total = count( $subs );
+
+	// Complete results
+	$result = sprintf( _n( 'Complete! %d forum subscription deleted.', 'Complete! %d forum subscriptions deleted.', $total, 'bbpress' ), bbp_number_format( $total ) );
+
+	return array( 0, sprintf( $statement, $result ) );
+}
Index: /tags/2.6.6/src/includes/admin/topics.php
===================================================================
--- /tags/2.6.6/src/includes/admin/topics.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/topics.php	(revision 7162)
@@ -0,0 +1,1259 @@
+<?php
+
+/**
+ * bbPress Topics Admin Class
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Topics_Admin' ) ) :
+/**
+ * Loads bbPress topics admin area
+ *
+ * @package bbPress
+ * @subpackage Administration
+ * @since 2.0.0 bbPress (r2464)
+ */
+class BBP_Topics_Admin {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * @var The post type of this admin component
+	 */
+	private $post_type = '';
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main bbPress topics admin loader
+	 *
+	 * @since 2.0.0 bbPress (r2515)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the admin hooks, actions and filters
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 * @since 2.6.0 bbPress (r6101) Added bulk actions
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Messages
+		add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
+
+		// Topic bulk actions, added in WordPress 4.7, see #WP16031.
+		if ( bbp_get_major_wp_version() >= 4.7 ) {
+			add_filter( 'bulk_actions-edit-topic',        array( $this, 'bulk_actions' ) );
+			add_filter( 'handle_bulk_actions-edit-topic', array( $this, 'handle_bulk_actions' ), 10, 3 );
+			add_filter( 'bulk_post_updated_messages',     array( $this, 'bulk_post_updated_messages' ), 10, 2 );
+		}
+
+		// Topic column headers.
+		add_filter( 'manage_' . $this->post_type . '_posts_columns',        array( $this, 'column_headers' ) );
+
+		// Topic columns (in post row)
+		add_action( 'manage_' . $this->post_type . '_posts_custom_column',  array( $this, 'column_data' ), 10, 2 );
+		add_filter( 'post_row_actions',                                     array( $this, 'row_actions' ), 10, 2 );
+
+		// Topic meta-box actions
+		add_action( 'add_meta_boxes', array( $this, 'attributes_metabox'    ) );
+		add_action( 'add_meta_boxes', array( $this, 'author_metabox'        ) );
+		add_action( 'add_meta_boxes', array( $this, 'replies_metabox'       ) );
+		add_action( 'add_meta_boxes', array( $this, 'engagements_metabox'   ) );
+		add_action( 'add_meta_boxes', array( $this, 'favorites_metabox'     ) );
+		add_action( 'add_meta_boxes', array( $this, 'subscriptions_metabox' ) );
+		add_action( 'add_meta_boxes', array( $this, 'comments_metabox'      ) );
+		add_action( 'save_post',      array( $this, 'save_meta_boxes'       ) );
+
+		// Check if there are any bbp_toggle_topic_* requests on admin_init, also have a message displayed
+		add_action( 'load-edit.php',  array( $this, 'toggle_topic'        ) );
+		add_action( 'load-edit.php',  array( $this, 'toggle_topic_notice' ) );
+
+		// Add ability to filter topics and replies per forum
+		add_filter( 'restrict_manage_posts', array( $this, 'filter_dropdown'  ) );
+		add_filter( 'bbp_request',           array( $this, 'filter_post_rows' ) );
+
+		// Empty spam
+		add_filter( 'manage_posts_extra_tablenav', array( $this, 'filter_empty_spam' ) );
+
+		// Contextual Help
+		add_action( 'load-edit.php',     array( $this, 'edit_help' ) );
+		add_action( 'load-post.php',     array( $this, 'new_help'  ) );
+		add_action( 'load-post-new.php', array( $this, 'new_help'  ) );
+	}
+
+	/**
+	 * Admin globals
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+		$this->post_type = bbp_get_topic_post_type();
+	}
+
+	/** Contextual Help *******************************************************/
+
+	/**
+	 * Contextual help for bbPress topic edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function edit_help() {
+
+		// Overview
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'overview',
+			'title'		=> __( 'Overview', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'This screen displays the individual topics on your site. You can customize the display of this screen to suit your workflow.', 'bbpress' ) . '</p>'
+		) );
+
+		// Screen Content
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'screen-content',
+			'title'		=> __( 'Screen Content', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can customize the display of this screen&#8217;s contents in a number of ways:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( 'You can hide/display columns based on your needs and decide how many topics to list per screen using the Screen Options tab.',                                                                               'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can filter the list of topics by topic status using the text links in the upper left to show All, Published, Draft, Pending, Trashed, Closed, or Spam  topics. The default view is to show all topics.', 'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can view topics in a simple title list or with an excerpt. Choose the view you prefer by clicking on the icons at the top of the list on the right.',                                                    'bbpress' ) . '</li>' .
+					'<li>' . __( 'You can refine the list to show only topics in a specific forum or from a specific month by using the dropdown menus above the topics list. Click the Filter button after making your selection.',                 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Available Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'action-links',
+			'title'		=> __( 'Available Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'Hovering over a row in the topics list will display action links that allow you to manage your topic. You can perform the following actions:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Edit</strong> takes you to the editing screen for that topic. You can also reach that screen by clicking on the topic title.',                            'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Stick</strong> will keep the selected topic &#8217;pinned&#8217; to the top the parent forum topic list.',                                                'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Stick <em>(to front)</em></strong> will keep the selected topic &#8217;pinned&#8217; to the top of ALL forums and be visable in any forums topics list.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Approve</strong> will change the status from pending to publish.',                                                                                        'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Close</strong> will mark the selected topic as &#8217;closed&#8217; and disable the ability to post new replies to it.',                                  'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Spam</strong> removes your topic from this list and places it in the spam queue, from which you can permanently delete it.',                              'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Trash</strong> removes your topic from this list and places it in the trash, from which you can permanently delete it.',                                  'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>View</strong> will take you to your live site to view the topic.',                                                                                        'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		// Bulk Actions
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'bulk-actions',
+			'title'		=> __( 'Bulk Actions', 'bbpress' ),
+			'content'	=>
+				'<p>' . __( 'You can also edit, spam, or move multiple topics to the trash at once. Select the topics you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.',           'bbpress' ) . '</p>' .
+				'<p>' . __( 'When using Bulk Edit, you can change the metadata (categories, author, etc.) for all selected topics at once. To remove a topic from the grouping, just click the x next to its name in the Bulk Edit area that appears.', 'bbpress' ) . '</p>'
+		) );
+
+		// Help Sidebar
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',     'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>',  'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Contextual help for bbPress topic edit page
+	 *
+	 * @since 2.0.0 bbPress (r3119)
+	 */
+	public function new_help() {
+
+		$customize_display = '<p>' . __( 'The title field and the big topic editing Area are fixed in place, but you can reposition all the other boxes using drag and drop, and can minimize or expand them by clicking the title bar of each box. Use the Screen Options tab to unhide more boxes (Excerpt, Send Trackbacks, Custom Fields, Discussion, Slug, Author) or to choose a 1- or 2-column layout for this screen.', 'bbpress' ) . '</p>';
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'customize-display',
+			'title'   => __( 'Customizing This Display', 'bbpress' ),
+			'content' => $customize_display,
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'title-topic-editor',
+			'title'   => __( 'Title and Topic Editor', 'bbpress' ),
+			'content' =>
+				'<p>' . __( '<strong>Title</strong> - Enter a title for your topic. After you enter a title, you&#8217;ll see the permalink below, which you can edit.', 'bbpress' ) . '</p>' .
+				'<p>' . __( '<strong>Topic Editor</strong> - Enter the text for your topic. There are two modes of editing: Visual and HTML. Choose the mode by clicking on the appropriate tab. Visual mode gives you a WYSIWYG editor. Click the last icon in the row to get a second row of controls. The HTML mode allows you to enter raw HTML along with your topic text. You can insert media files by clicking the icons above the topic editor and following the directions. You can go to the distraction-free writing screen via the Fullscreen icon in Visual mode (second to last in the top row) or the Fullscreen button in HTML mode (last in the row). Once there, you can make buttons visible by hovering over the top area. Exit Fullscreen back to the regular topic editor.', 'bbpress' ) . '</p>'
+		) );
+
+		$publish_box = '<p>' . __( '<strong>Publish</strong> - You can set the terms of publishing your topic in the Publish box. For Status, Visibility, and Publish (immediately), click on the Edit link to reveal more options. Visibility includes options for password-protecting a topic or making it stay at the top of your blog indefinitely (sticky). Publish (immediately) allows you to set a future or past date and time, so you can schedule a topic to be published in the future or backdate a topic.', 'bbpress' ) . '</p>';
+
+		if ( current_theme_supports( 'topic-thumbnails' ) && post_type_supports( bbp_get_topic_post_type(), 'thumbnail' ) ) {
+			$publish_box .= '<p>' . __( '<strong>Featured Image</strong> - This allows you to associate an image with your topic without inserting it. This is usually useful only if your theme makes use of the featured image as a topic thumbnail on the home page, a custom header, etc.', 'bbpress' ) . '</p>';
+		}
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'topic-attributes',
+			'title'   => __( 'Topic Attributes', 'bbpress' ),
+			'content' =>
+				'<p>' . __( 'Select the attributes that your topic should have:', 'bbpress' ) . '</p>' .
+				'<ul>' .
+					'<li>' . __( '<strong>Forum</strong> dropdown determines the parent forum that the topic belongs to. Select the forum or category from the dropdown, or leave the default "No forum" to post the topic without an assigned forum.', 'bbpress' ) . '</li>' .
+					'<li>' . __( '<strong>Topic Type</strong> dropdown indicates the sticky status of the topic. Selecting the super sticky option would stick the topic to the front of your forums, i.e. the topic index, sticky option would stick the topic to its respective forum. Selecting normal would not stick the topic anywhere.', 'bbpress' ) . '</li>' .
+				'</ul>'
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'publish-box',
+			'title'   => __( 'Publish Box', 'bbpress' ),
+			'content' => $publish_box,
+		) );
+
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'bbpress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.bbpress.org" target="_blank">bbPress Documentation</a>',    'bbpress' ) . '</p>' .
+			'<p>' . __( '<a href="https://bbpress.org/forums/" target="_blank">bbPress Support Forums</a>', 'bbpress' ) . '</p>'
+		);
+	}
+
+	/**
+	 * Add spam/unspam bulk actions to the bulk action dropdown.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param array $actions The list of bulk actions.
+	 * @return array The filtered list of bulk actions.
+	 */
+	public function bulk_actions( $actions ) {
+
+		if ( current_user_can( 'moderate' ) ) {
+			if ( bbp_get_spam_status_id() === get_query_var( 'post_status' ) ) {
+				$actions['unspam'] = esc_html__( 'Unspam', 'bbpress' );
+			} else {
+				$actions['spam'] = esc_html__( 'Spam', 'bbpress' );
+			}
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Add custom bulk action updated messages for topics.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param array $bulk_messages Arrays of messages, each keyed by the corresponding post type.
+	 * @param array $bulk_counts   Array of item counts for each message, used to build internationalized strings.
+	 */
+	public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
+		$bulk_messages['topic']['updated'] = _n( '%s topic updated.', '%s topics updated.', $bulk_counts['updated'], 'bbpress' );
+		$bulk_messages['topic']['locked']  = ( 1 === $bulk_counts['locked'] )
+			? __( '1 topic not updated, somebody is editing it.', 'bbpress' )
+			: _n( '%s topic not updated, somebody is editing it.', '%s topics not updated, somebody is editing them.', $bulk_counts['locked'], 'bbpress' );
+
+		return $bulk_messages;
+	}
+
+	/**
+	 * Handle spam/unspam bulk actions.
+	 *
+	 * @since 2.6.0 bbPress (r6101)
+	 *
+	 * @param string $sendback The sendback URL.
+	 * @param string $doaction The action to be taken.
+	 * @param array  $post_ids The post IDS to take the action on.
+	 * @return string The sendback URL.
+	 */
+	public function handle_bulk_actions( $sendback, $doaction, $post_ids ) {
+
+		$sendback = remove_query_arg( array( 'spam', 'unspam' ), $sendback );
+		$updated = $locked = 0;
+
+		if ( 'spam' === $doaction ) {
+
+			foreach ( (array) $post_ids as $post_id ) {
+				if ( ! current_user_can( 'moderate', $post_id ) ) {
+					wp_die( esc_html__( 'Sorry, you are not allowed to spam this item.', 'bbpress' ) );
+				}
+
+				if ( wp_check_post_lock( $post_id ) ) {
+					$locked++;
+					continue;
+				}
+
+				if ( ! bbp_spam_topic( $post_id ) ) {
+					wp_die( esc_html__( 'Error in spamming topic.', 'bbpress' ) );
+				}
+
+				$updated++;
+			}
+
+			$sendback = add_query_arg( array(
+				'updated' => $updated,
+				'ids'     => implode( ',', $post_ids ),
+				'locked'  => $locked
+			), $sendback );
+
+		} elseif ( 'unspam' === $doaction ) {
+
+			foreach ( (array) $post_ids as $post_id ) {
+				if ( ! current_user_can( 'moderate', $post_id ) ) {
+					wp_die( esc_html__( 'Sorry, you are not allowed to unspam this topic.', 'bbpress' ) );
+				}
+
+				if ( wp_check_post_lock( $post_id ) ) {
+					$locked++;
+					continue;
+				}
+
+				if ( ! bbp_unspam_topic( $post_id ) ) {
+					wp_die( esc_html__( 'Error in unspamming topic.', 'bbpress' ) );
+				}
+
+				$updated++;
+			}
+
+			$sendback = add_query_arg( array(
+				'updated' => $updated,
+				'ids'     => implode( ',', $post_ids ),
+				'locked'  => $locked
+			), $sendback );
+		}
+
+		return $sendback;
+	}
+
+	/**
+	 * Add the topic attributes meta-box
+	 *
+	 * @since 2.0.0 bbPress (r2744)
+	 */
+	public function attributes_metabox() {
+		add_meta_box(
+			'bbp_topic_attributes',
+			esc_html__( 'Topic Attributes', 'bbpress' ),
+			'bbp_topic_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the author info meta-box
+	 *
+	 * @since 2.0.0 bbPress (r2828)
+	 */
+	public function author_metabox() {
+
+		// Bail if post_type is not a topic
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_author_metabox',
+			esc_html__( 'Author Information', 'bbpress' ),
+			'bbp_author_metabox',
+			$this->post_type,
+			'side',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the replies meta-box
+	 *
+	 * Allows viewing & moderating of replies to a topic, based on the way
+	 * comments are visible on a blog post.
+	 *
+	 * @since 2.6.0 bbPress (r5886)
+	 */
+	public function replies_metabox() {
+
+		// Bail if post_type is not a reply
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_topic_replies_metabox',
+			esc_html__( 'Replies', 'bbpress' ),
+			'bbp_topic_replies_metabox',
+			$this->post_type,
+			'normal',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the engagements meta-box
+	 *
+	 * Allows viewing of users who have engaged in a topic.
+	 *
+	 * @since 2.6.0 bbPress (r6333)
+	 */
+	public function engagements_metabox() {
+
+		// Bail when creating a new topic
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Bail if no engagements
+		if ( ! bbp_is_engagements_active() ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_topic_engagements_metabox',
+			esc_html__( 'Engagements', 'bbpress' ),
+			'bbp_topic_engagements_metabox',
+			$this->post_type,
+			'side',
+			'low'
+		);
+	}
+
+	/**
+	 * Add the favorites meta-box
+	 *
+	 * Allows viewing of users who have favorited a topic.
+	 *
+	 * @since 2.6.0 bbPress (r6197)
+	 */
+	public function favorites_metabox() {
+
+		// Bail if post_type is not a reply
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Bail if no favorites
+		if ( ! bbp_is_favorites_active() ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_topic_favorites_metabox',
+			esc_html__( 'Favorites', 'bbpress' ),
+			'bbp_topic_favorites_metabox',
+			$this->post_type,
+			'normal',
+			'high'
+		);
+	}
+
+	/**
+	 * Add the subscriptions meta-box
+	 *
+	 * Allows viewing of users who have subscribed to a topic.
+	 *
+	 * @since 2.6.0 bbPress (r6197)
+	 */
+	public function subscriptions_metabox() {
+
+		// Bail if post_type is not a reply
+		if ( empty( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
+			return;
+		}
+
+		// Bail if no subscriptions
+		if ( ! bbp_is_subscriptions_active() ) {
+			return;
+		}
+
+		// Add the meta-box
+		add_meta_box(
+			'bbp_topic_subscriptions_metabox',
+			esc_html__( 'Subscriptions', 'bbpress' ),
+			'bbp_topic_subscriptions_metabox',
+			$this->post_type,
+			'normal',
+			'high'
+		);
+	}
+
+	/**
+	 * Remove comments & discussion meta-boxes if comments are not supported
+	 *
+	 * @since 2.6.0 bbPress (r6186)
+	 */
+	public function comments_metabox() {
+		if ( ! post_type_supports( $this->post_type, 'comments' ) ) {
+			remove_meta_box( 'commentstatusdiv', $this->post_type, 'normal' );
+			remove_meta_box( 'commentsdiv',      $this->post_type, 'normal' );
+		}
+	}
+
+	/**
+	 * Pass the topic attributes for processing
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @param int $topic_id Topic id
+	 * @return int Parent id
+	 */
+	public function save_meta_boxes( $topic_id ) {
+
+		// Bail if doing an autosave
+		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
+			return $topic_id;
+		}
+
+		// Bail if not a post request
+		if ( ! bbp_is_post_request() ) {
+			return $topic_id;
+		}
+
+		// Check action exists
+		if ( empty( $_POST['action'] ) ) {
+			return $topic_id;
+		}
+
+		// Nonce check
+		if ( empty( $_POST['bbp_topic_metabox'] ) || ! wp_verify_nonce( $_POST['bbp_topic_metabox'], 'bbp_topic_metabox_save' ) ) {
+			return $topic_id;
+		}
+
+		// Bail if current user cannot edit this topic
+		if ( ! current_user_can( 'edit_topic', $topic_id ) ) {
+			return $topic_id;
+		}
+
+		// Get the forum ID
+		$forum_id = ! empty( $_POST['parent_id'] ) ? (int) $_POST['parent_id'] : 0;
+
+		// Get topic author data
+		$anonymous_data = bbp_filter_anonymous_post_data();
+		$author_id      = bbp_get_topic_author_id( $topic_id );
+		$is_edit        = ( isset( $_POST['hidden_post_status'] ) && ( $_POST['hidden_post_status'] !== 'draft' ) );
+
+		// Formally update the topic
+		bbp_update_topic( $topic_id, $forum_id, $anonymous_data, $author_id, $is_edit );
+
+		// Allow other fun things to happen
+		do_action( 'bbp_topic_attributes_metabox_save', $topic_id, $forum_id       );
+		do_action( 'bbp_author_metabox_save',           $topic_id, $anonymous_data );
+
+		return $topic_id;
+	}
+
+	/**
+	 * Toggle topic
+	 *
+	 * Handles the admin-side opening/closing, sticking/unsticking and
+	 * spamming/unspamming of topics
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 */
+	public function toggle_topic() {
+
+		// Bail if not a topic toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['action'] ) || empty( $_GET['topic_id'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed action
+		$action = sanitize_key( $_GET['action'] );
+		if ( empty( $action ) || ! in_array( $action, $this->get_allowed_action_toggles(), true ) ) {
+			return;
+		}
+
+		// Bail if topic is missing
+		$topic_id = bbp_get_topic_id( $_GET['topic_id'] );
+		if ( ! bbp_get_topic( $topic_id ) ) {
+			wp_die( esc_html__( 'The topic was not found.', 'bbpress' ) );
+		}
+
+		// What is the user doing here?
+		if ( ! current_user_can( 'moderate', $topic_id ) ) {
+			wp_die( esc_html__( 'You do not have permission to do that.', 'bbpress' ) );
+		}
+
+		// Defaults
+		$post_data = array( 'ID' => $topic_id );
+		$message   = '';
+		$success   = false;
+
+		switch ( $action ) {
+			case 'bbp_toggle_topic_approve' :
+				check_admin_referer( 'approve-topic_' . $topic_id );
+
+				$is_approve = bbp_is_topic_public( $topic_id );
+				$message    = ( true === $is_approve )
+					? 'unapproved'
+					: 'approved';
+				$success    = ( true === $is_approve )
+					? bbp_unapprove_topic( $topic_id )
+					: bbp_approve_topic( $topic_id );
+
+				break;
+
+			case 'bbp_toggle_topic_close' :
+				check_admin_referer( 'close-topic_' . $topic_id );
+
+				$is_open = bbp_is_topic_open( $topic_id );
+				$message = ( true === $is_open )
+					? 'closed'
+					: 'opened';
+				$success = ( true === $is_open )
+					? bbp_close_topic( $topic_id )
+					: bbp_open_topic( $topic_id );
+
+				break;
+
+			case 'bbp_toggle_topic_stick' :
+				check_admin_referer( 'stick-topic_' . $topic_id );
+
+				$is_sticky = bbp_is_topic_sticky( $topic_id );
+				$is_super  = ( false === $is_sticky ) && ! empty( $_GET['super'] ) && ( "1" === $_GET['super'] )
+					? true
+					: false;
+				$message   = ( true  === $is_sticky )
+					? 'unstuck'
+					: 'stuck';
+				$message   = ( true  === $is_super )
+					? 'super_sticky'
+					: $message;
+				$success   = ( true  === $is_sticky )
+					? bbp_unstick_topic( $topic_id )
+					: bbp_stick_topic( $topic_id, $is_super );
+
+				break;
+
+			case 'bbp_toggle_topic_spam'  :
+				check_admin_referer( 'spam-topic_' . $topic_id );
+
+				$is_spam = bbp_is_topic_spam( $topic_id );
+				$message = ( true === $is_spam )
+					? 'unspammed'
+					: 'spammed';
+				$success = ( true === $is_spam )
+					? bbp_unspam_topic( $topic_id )
+					: bbp_spam_topic( $topic_id );
+
+				break;
+		}
+
+		// Setup the message
+		$retval = array(
+			'bbp_topic_toggle_notice' => $message,
+			'topic_id'                => $topic_id
+		);
+
+		// Prepare for failure
+		if ( ( false === $success ) || is_wp_error( $success ) ) {
+			$retval['failed'] = '1';
+		}
+
+		// Filter all message args
+		$retval = apply_filters( 'bbp_toggle_topic_action_admin', $retval, $topic_id, $action );
+
+		// Do additional topic toggle actions (admin side)
+		do_action( 'bbp_toggle_topic_admin', $success, $post_data, $action, $retval );
+
+		// Redirect back to the topic
+		$redirect = add_query_arg( $retval, remove_query_arg( array( 'action', 'topic_id' ) ) );
+		bbp_redirect( $redirect );
+	}
+
+	/**
+	 * Toggle topic notices
+	 *
+	 * Display the success/error notices from
+	 * {@link BBP_Admin::toggle_topic()}
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 */
+	public function toggle_topic_notice() {
+
+		// Bail if missing topic toggle action
+		if ( ! bbp_is_get_request() || empty( $_GET['topic_id'] ) || empty( $_GET['bbp_topic_toggle_notice'] ) ) {
+			return;
+		}
+
+		// Bail if not an allowed notice
+		$notice = sanitize_key( $_GET['bbp_topic_toggle_notice'] );
+		if ( empty( $notice ) || ! in_array( $notice, $this->get_allowed_notice_toggles(), true ) ) {
+			return;
+		}
+
+		// Bail if no topic_id or notice
+		$topic_id = bbp_get_topic_id( $_GET['topic_id'] );
+		if (  empty( $topic_id ) ) {
+			return;
+		}
+
+		// Bail if topic is missing
+		if ( ! bbp_get_topic( $topic_id ) ) {
+			return;
+		}
+
+		// Use the title in the responses
+		$topic_title = bbp_get_topic_title( $topic_id );
+		$is_failure  = ! empty( $_GET['failed'] );
+		$message     = '';
+
+		// Which notice?
+		switch ( $notice ) {
+			case 'opened'    :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem opening the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully opened.',             'bbpress' ), $topic_title );
+				break;
+
+			case 'closed'    :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem closing the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully closed.',             'bbpress' ), $topic_title );
+				break;
+
+			case 'super_sticky' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem sticking the topic "%1$s" to front.', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully stuck to front.',               'bbpress' ), $topic_title );
+				break;
+
+			case 'stuck'   :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem sticking the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully stuck.',               'bbpress' ), $topic_title );
+				break;
+
+			case 'unstuck' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem unsticking the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully unstuck.',               'bbpress' ), $topic_title );
+				break;
+
+			case 'spammed'   :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem marking the topic "%1$s" as spam.', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully marked as spam.',             'bbpress' ), $topic_title );
+				break;
+
+			case 'unspammed' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem unmarking the topic "%1$s" as spam.', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully unmarked as spam.',             'bbpress' ), $topic_title );
+				break;
+
+			case 'approved'   :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem approving the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully approved.',             'bbpress' ), $topic_title );
+				break;
+
+			case 'unapproved' :
+				$message = ( $is_failure === true )
+					? sprintf( esc_html__( 'There was a problem unapproving the topic "%1$s".', 'bbpress' ), $topic_title )
+					: sprintf( esc_html__( 'Topic "%1$s" successfully unapproved.',             'bbpress' ), $topic_title );
+				break;
+		}
+
+		// Do additional topic toggle notice filters (admin side)
+		$message = apply_filters( 'bbp_toggle_topic_notice_admin', $message, $topic_id, $notice, $is_failure );
+		$class   = ( $is_failure === true )
+			? 'error'
+			: 'updated';
+
+		// Add the notice
+		bbp_admin()->add_notice( $message, $class, true );
+	}
+
+	/**
+	 * Returns an array of keys used to sort row actions
+	 *
+	 * @since 2.6.0 bbPress (r6771)
+	 *
+	 * @return array
+	 */
+	private function get_row_action_sort_order() {
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_topics_row_action_sort_order', array(
+			'edit',
+			'stick',
+			'approved',
+			'unapproved',
+			'closed',
+			'spam',
+			'unspam',
+			'trash',
+			'untrash',
+			'delete',
+			'view'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_notice_toggles() {
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_topics_allowed_notice_toggles', array(
+			'opened',
+			'closed',
+			'super_sticky',
+			'stuck',
+			'unstuck',
+			'spammed',
+			'unspammed',
+			'approved',
+			'unapproved'
+		) );
+	}
+
+	/**
+	 * Returns an array of notice toggles
+	 *
+	 * @since 2.6.0 bbPress (r6396)
+	 *
+	 * @return array
+	 */
+	private function get_allowed_action_toggles() {
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_topics_allowed_action_toggles', array(
+			'bbp_toggle_topic_close',
+			'bbp_toggle_topic_stick',
+			'bbp_toggle_topic_spam',
+			'bbp_toggle_topic_approve'
+		) );
+	}
+
+	/**
+	 * Manage the column headers for the topics page
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param array $columns The columns
+	 *
+	 * @return array $columns bbPress topic columns
+	 */
+	public function column_headers( $columns ) {
+		$columns = array(
+			'cb'                    => '<input type="checkbox" />',
+			'title'                 => esc_html__( 'Topics',    'bbpress' ),
+			'bbp_topic_forum'       => esc_html__( 'Forum',     'bbpress' ),
+			'bbp_topic_reply_count' => esc_html__( 'Replies',   'bbpress' ),
+			'bbp_topic_voice_count' => esc_html__( 'Voices',    'bbpress' ),
+			'bbp_topic_author'      => esc_html__( 'Author',    'bbpress' ),
+			'bbp_topic_created'     => esc_html__( 'Created',   'bbpress' ),
+			'bbp_topic_freshness'   => esc_html__( 'Last Post', 'bbpress' )
+		);
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_admin_topics_column_headers', $columns );
+	}
+
+	/**
+	 * Print extra columns for the topics page
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param string $column Column
+	 * @param int $topic_id Topic id
+	 */
+	public function column_data( $column, $topic_id ) {
+
+		// Get topic forum ID
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Populate column data
+		switch ( $column ) {
+
+			// Forum
+			case 'bbp_topic_forum' :
+
+				// Get title
+				$forum_title = ! empty( $forum_id )
+					? bbp_get_forum_title( $forum_id )
+					: '';
+
+				// Output forum name
+				if ( ! empty( $forum_title ) ) {
+					echo $forum_title;
+
+				// Output dash
+				} else {
+					?>
+					<span aria-hidden="true">&mdash;</span>
+					<span class="screen-reader-text"><?php esc_html_e( 'No forum', 'bbpress' ); ?></span>
+					<?php
+				}
+
+				break;
+
+			// Reply Count
+			case 'bbp_topic_reply_count' :
+				bbp_topic_reply_count( $topic_id );
+				break;
+
+			// Reply Count
+			case 'bbp_topic_voice_count' :
+				bbp_topic_voice_count( $topic_id );
+				break;
+
+			// Author
+			case 'bbp_topic_author' :
+				bbp_topic_author_display_name( $topic_id );
+				break;
+
+			// Freshness
+			case 'bbp_topic_created':
+				printf( '%1$s <br /> %2$s',
+					get_the_date(),
+					esc_attr( get_the_time() )
+				);
+
+				break;
+
+			// Freshness
+			case 'bbp_topic_freshness' :
+				$last_active = bbp_get_topic_last_active_time( $topic_id, false );
+				if ( ! empty( $last_active ) ) {
+					echo esc_html( $last_active );
+				} else {
+					esc_html_e( 'No Replies', 'bbpress' ); // This should never happen
+				}
+
+				break;
+
+			// Do an action for anything else
+			default :
+				do_action( 'bbp_admin_topics_column_data', $column, $topic_id );
+				break;
+		}
+	}
+
+	/**
+	 * Topic Row actions
+	 *
+	 * Remove the quick-edit action link under the topic title and add the
+	 * content and close/stick/spam links
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param array  $actions Actions
+	 * @param object $topic   Topic object
+	 *
+	 * @return array $actions Actions
+	 */
+	public function row_actions( $actions = array(), $topic = false ) {
+
+		// Disable quick edit (too much to do here)
+		unset( $actions['inline hide-if-no-js'] );
+
+		// View link
+		$view_link = bbp_get_topic_permalink( $topic->ID );
+
+		// Maybe add view=all
+		if ( ! in_array( $topic->post_status, array( bbp_get_closed_status_id(), bbp_get_public_status_id() ), true ) ) {
+			$view_link = bbp_add_view_all( $view_link, true );
+		}
+
+		// Show view link if it's not set, the topic is trashed and the user can view trashed topics
+		if ( empty( $actions['view'] ) && ( bbp_get_trash_status_id() === $topic->post_status ) && current_user_can( 'view_trash' ) ) {
+			$actions['view'] = '<a href="' . esc_url( $view_link ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'bbpress' ), bbp_get_topic_title( $topic->ID ) ) ) . '" rel="permalink">' . esc_html__( 'View', 'bbpress' ) . '</a>';
+		}
+
+		// Only show the actions if the user is capable of viewing them :)
+		if ( current_user_can( 'moderate', $topic->ID ) ) {
+
+			// Pending
+			// Show the 'approve' and 'view' link on pending posts only and 'unapprove' on published posts only
+			$approve_uri = wp_nonce_url( add_query_arg( array( 'topic_id' => $topic->ID, 'action' => 'bbp_toggle_topic_approve' ), remove_query_arg( array( 'bbp_topic_toggle_notice', 'topic_id', 'failed', 'super' ) ) ), 'approve-topic_' . $topic->ID );
+			if ( bbp_is_topic_public( $topic->ID ) ) {
+				$actions['unapproved'] = '<a href="' . esc_url( $approve_uri ) . '" title="' . esc_attr__( 'Unapprove this topic', 'bbpress' ) . '">' . _x( 'Unapprove', 'Unapprove Topic', 'bbpress' ) . '</a>';
+			} else {
+
+				// Do not show 'approve' if already public
+				if ( ! bbp_is_topic_public( $topic->ID ) ) {
+					$actions['approved'] = '<a href="' . esc_url( $approve_uri ) . '" title="' . esc_attr__( 'Approve this topic',   'bbpress' ) . '">' . _x( 'Approve',   'Approve Topic',   'bbpress' ) . '</a>';
+				}
+
+				// Modify the view link
+				$actions['view'] = '<a href="' . esc_url( $view_link   ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'bbpress' ), bbp_get_topic_title( $topic->ID ) ) ) . '" rel="permalink">' . esc_html__( 'View', 'bbpress' ) . '</a>';
+			}
+
+			// Close
+			// Show the 'close' and 'open' link on published and closed posts only
+			if ( bbp_is_topic_public( $topic->ID ) ) {
+				$close_uri = wp_nonce_url( add_query_arg( array( 'topic_id' => $topic->ID, 'action' => 'bbp_toggle_topic_close' ), remove_query_arg( array( 'bbp_topic_toggle_notice', 'topic_id', 'failed', 'super' ) ) ), 'close-topic_' . $topic->ID );
+				if ( bbp_is_topic_open( $topic->ID ) ) {
+					$actions['closed'] = '<a href="' . esc_url( $close_uri ) . '" title="' . esc_attr__( 'Close this topic', 'bbpress' ) . '">' . _x( 'Close', 'Close a Topic', 'bbpress' ) . '</a>';
+				} else {
+					$actions['closed'] = '<a href="' . esc_url( $close_uri ) . '" title="' . esc_attr__( 'Open this topic',  'bbpress' ) . '">' . _x( 'Open',  'Open a Topic',  'bbpress' ) . '</a>';
+				}
+			}
+
+			// Sticky
+			// Dont show sticky if topic is spam, trash or pending
+			if ( ! bbp_is_topic_spam( $topic->ID ) && ! bbp_is_topic_trash( $topic->ID ) && ! bbp_is_topic_pending( $topic->ID ) ) {
+				$stick_uri = wp_nonce_url( add_query_arg( array( 'topic_id' => $topic->ID, 'action' => 'bbp_toggle_topic_stick' ), remove_query_arg( array( 'bbp_topic_toggle_notice', 'topic_id', 'failed', 'super' ) ) ), 'stick-topic_'  . $topic->ID );
+				if ( bbp_is_topic_sticky( $topic->ID ) ) {
+					$actions['stick'] = '<a href="' . esc_url( $stick_uri ) . '" title="' . esc_attr__( 'Unstick this topic', 'bbpress' ) . '">' . esc_html__( 'Unstick', 'bbpress' ) . '</a>';
+				} else {
+					$super_uri        = wp_nonce_url( add_query_arg( array( 'topic_id' => $topic->ID, 'action' => 'bbp_toggle_topic_stick', 'super' => '1' ), remove_query_arg( array( 'bbp_topic_toggle_notice', 'topic_id', 'failed', 'super' ) ) ), 'stick-topic_'  . $topic->ID );
+					$actions['stick'] = '<a href="' . esc_url( $stick_uri ) . '" title="' . esc_attr__( 'Stick this topic to its forum', 'bbpress' ) . '">' . esc_html__( 'Stick', 'bbpress' ) . '</a> <a href="' . esc_url( $super_uri ) . '" title="' . esc_attr__( 'Stick this topic to front', 'bbpress' ) . '">' . esc_html__( '(to front)', 'bbpress' ) . '</a>';
+				}
+			}
+
+			// Spam
+			$spam_uri = wp_nonce_url( add_query_arg( array( 'topic_id' => $topic->ID, 'action' => 'bbp_toggle_topic_spam' ), remove_query_arg( array( 'bbp_topic_toggle_notice', 'topic_id', 'failed', 'super' ) ) ), 'spam-topic_'  . $topic->ID );
+			if ( ! bbp_is_topic_spam( $topic->ID ) ) {
+				$actions['spam'] = '<a href="' . esc_url( $spam_uri ) . '" title="' . esc_attr__( 'Mark this topic as spam',    'bbpress' ) . '">' . esc_html__( 'Spam',     'bbpress' ) . '</a>';
+			} else {
+				$actions['unspam'] = '<a href="' . esc_url( $spam_uri ) . '" title="' . esc_attr__( 'Mark the topic as not spam', 'bbpress' ) . '">' . esc_html__( 'Not Spam', 'bbpress' ) . '</a>';
+			}
+		}
+
+		// Do not show trash links for spam topics, or spam links for trashed topics
+		if ( current_user_can( 'delete_topic', $topic->ID ) ) {
+			$trash_days = bbp_get_trash_days( bbp_get_topic_post_type() );
+
+			if ( bbp_get_trash_status_id() === $topic->post_status ) {
+				$post_type_object   = get_post_type_object( bbp_get_topic_post_type() );
+				$actions['untrash'] = "<a title='" . esc_attr__( 'Restore this item from the Trash', 'bbpress' ) . "' href='" . esc_url( wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $topic->ID ) ), 'untrash-post_' . $topic->ID ) ) . "'>" . esc_html__( 'Restore', 'bbpress' ) . "</a>";
+			} elseif ( ! empty( $trash_days ) ) {
+				$actions['trash'] = "<a class='submitdelete' title='" . esc_attr__( 'Move this item to the Trash', 'bbpress' ) . "' href='" . esc_url( get_delete_post_link( $topic->ID ) ) . "'>" . esc_html__( 'Trash', 'bbpress' ) . "</a>";
+			}
+
+			if ( ( bbp_get_trash_status_id() === $topic->post_status ) || empty( $trash_days ) ) {
+				$actions['delete'] = "<a class='submitdelete' title='" . esc_attr__( 'Delete this item permanently', 'bbpress' ) . "' href='" . esc_url( get_delete_post_link( $topic->ID, '', true ) ) . "'>" . esc_html__( 'Delete Permanently', 'bbpress' ) . "</a>";
+			}
+		}
+
+		// Sort & return
+		return $this->sort_row_actions( $actions );
+	}
+
+	/**
+	 * Sort row actions by key
+	 *
+	 * @since 2.6.0
+	 *
+	 * @param array $actions
+	 *
+	 * @return array
+	 */
+	private function sort_row_actions( $actions = array() ) {
+
+		// Return value
+		$retval = array();
+
+		// Known row actions, in sort order
+		$known_actions = $this->get_row_action_sort_order();
+
+		// Sort known actions, and keep any unknown ones
+		foreach ( $known_actions as $key ) {
+			if ( isset( $actions[ $key ] ) ) {
+				$retval[ $key ] = $actions[ $key ];
+				unset( $actions[ $key ] );
+			}
+		}
+
+		// Combine & return
+		return $retval + $actions;
+	}
+
+	/**
+	 * Add forum dropdown to topic and reply list table filters
+	 *
+	 * @since 2.0.0 bbPress (r2991)
+	 *
+	 * @return bool False. If post type is not topic or reply
+	 */
+	public function filter_dropdown() {
+
+		// Get which forum is selected
+		$selected = ! empty( $_GET['bbp_forum_id'] )
+			? (int) $_GET['bbp_forum_id']
+			: 0;
+
+		// Show the forums dropdown
+		bbp_dropdown( array(
+			'selected'  => $selected,
+			'show_none' => esc_html__( 'In all forums', 'bbpress' )
+		) );
+	}
+
+	/**
+	 * Add "Empty Spam" button for moderators
+	 *
+	 * @since 2.6.0 bbPress (r6791)
+	 */
+	public function filter_empty_spam() {
+
+		// Bail if not viewing spam
+		if ( empty( $_GET['post_status'] ) || ( bbp_get_spam_status_id() !== $_GET['post_status'] ) && current_user_can( 'moderate' ) ) {
+			return;
+		}
+
+		?>
+
+		<div class="alignleft actions"><?php
+
+			// Output the nonce & button
+			wp_nonce_field( 'bulk-destroy', '_destroy_nonce' );
+			submit_button(
+				esc_attr__( 'Empty Spam', 'bbpress' ),
+				'button-secondary apply',
+				'delete_all',
+				false
+			);
+
+		?></div><?php
+	}
+
+	/**
+	 * Adjust the request query and include the forum id
+	 *
+	 * @since 2.0.0 bbPress (r2991)
+	 *
+	 * @param array $query_vars Query variables from {@link WP_Query}
+	 * @return array Processed Query Vars
+	 */
+	function filter_post_rows( $query_vars ) {
+
+		// Add post_parent query_var if one is present
+		if ( ! empty( $_GET['bbp_forum_id'] ) ) {
+			$query_vars['meta_key']   = '_bbp_forum_id';
+			$query_vars['meta_type']  = 'NUMERIC';
+			$query_vars['meta_value'] = $_GET['bbp_forum_id'];
+		}
+
+		// Return manipulated query_vars
+		return $query_vars;
+	}
+
+	/**
+	 * Custom user feedback messages for topic post type
+	 *
+	 * @since 2.0.0 bbPress (r3080)
+	 *
+	 * @global int $post_ID
+	 *
+	 * @param array $messages
+	 *
+	 * @return array
+	 */
+	public function updated_messages( $messages ) {
+		global $post_ID;
+
+		// URL for the current topic
+		$topic_url = bbp_get_topic_permalink( $post_ID );
+
+		// Current topic's post_date
+		$post_date = bbp_get_global_post_field( 'post_date', 'raw' );
+
+		// Messages array
+		$messages[ $this->post_type ] = array(
+			0 =>  '', // Left empty on purpose
+
+			// Updated
+			1 =>  sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Topic updated.', 'bbpress' ),
+				$topic_url,
+				esc_html__( 'View topic', 'bbpress' )
+			),
+
+			// Custom field updated
+			2 => esc_html__( 'Custom field updated.', 'bbpress' ),
+
+			// Custom field deleted
+			3 => esc_html__( 'Custom field deleted.', 'bbpress' ),
+
+			// Topic updated
+			4 => esc_html__( 'Topic updated.', 'bbpress' ),
+
+			// Restored from revision
+			// translators: %s: date and time of the revision
+			5 => isset( $_GET['revision'] )
+					? sprintf( esc_html__( 'Topic restored to revision from %s', 'bbpress' ), wp_post_revision_title( (int) $_GET['revision'], false ) )
+					: false,
+
+			// Topic created
+			6 => sprintf(
+				'%1$s <a href="%2$s">%3$s</a>',
+				esc_html__( 'Topic created.', 'bbpress' ),
+				$topic_url,
+				esc_html__( 'View topic', 'bbpress' )
+			),
+
+			// Topic saved
+			7 => esc_html__( 'Topic saved.', 'bbpress' ),
+
+			// Topic submitted
+			8 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Topic submitted.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $topic_url ) ),
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+
+			// Topic scheduled
+			9 => sprintf(
+				'%1$s <a target="_blank" href="%2$s">%3$s</a>',
+				sprintf(
+					esc_html__( 'Topic scheduled for: %s.', 'bbpress' ),
+					// translators: Publish box date format, see http://php.net/date
+					'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'bbpress' ), strtotime( $post_date ) ) . '</strong>'
+				),
+				$topic_url,
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+
+			// Topic draft updated
+			10 => sprintf(
+				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
+				esc_html__( 'Topic draft updated.', 'bbpress' ),
+				esc_url( add_query_arg( 'preview', 'true', $topic_url ) ),
+				esc_html__( 'Preview topic', 'bbpress' )
+			),
+		);
+
+		return $messages;
+	}
+}
+endif; // class_exists check
+
+/**
+ * Setup bbPress Topics Admin
+ *
+ * This is currently here to make hooking and unhooking of the admin UI easy.
+ * It could use dependency injection in the future, but for now this is easier.
+ *
+ * @since 2.0.0 bbPress (r2596)
+ *
+ * @param WP_Screen $current_screen Current screen object
+ */
+function bbp_admin_topics( $current_screen ) {
+
+	// Bail if not a forum screen
+	if ( empty( $current_screen->post_type ) || ( bbp_get_topic_post_type() !== $current_screen->post_type ) ) {
+		return;
+	}
+
+	// Init the topics admin
+	bbp_admin()->topics = new BBP_Topics_Admin();
+}
Index: /tags/2.6.6/src/includes/admin/users.php
===================================================================
--- /tags/2.6.6/src/includes/admin/users.php	(revision 7162)
+++ /tags/2.6.6/src/includes/admin/users.php	(revision 7162)
@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * bbPress Users Admin Class
+ *
+ * @package bbPress
+ * @subpackage Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Users_Admin' ) ) :
+/**
+ * Loads bbPress users admin area
+ *
+ * @package bbPress
+ * @subpackage Administration
+ * @since 2.0.0 bbPress (r2464)
+ */
+class BBP_Users_Admin {
+
+	/**
+	 * The bbPress users admin loader
+	 *
+	 * @since 2.0.0 bbPress (r2515)
+	 */
+	public function __construct() {
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the admin hooks, actions and filters
+	 *
+	 * @since 2.0.0 bbPress (r2646)
+	 *
+	 * @access private
+	 */
+	function setup_actions() {
+
+		// Bail if in network admin
+		if ( is_network_admin() ) {
+			return;
+		}
+
+		// User profile edit/display actions
+		add_action( 'edit_user_profile', array( $this, 'secondary_role_display' ) );
+
+		// WordPress user screen
+		// Remvove the bottom list table "change forum role" dropdown from WordPress < 4.6.
+		// See https://bbpress.trac.wordpress.org/ticket/2906.
+		if ( bbp_get_major_wp_version() < 4.6 ) {
+			add_action( 'restrict_manage_users',  array( __CLASS__, 'user_role_bulk_dropdown' )    );
+		} else {
+			add_action( 'restrict_manage_users',  array( $this, 'user_role_bulk_dropdown' ), 10, 1 );
+		}
+		add_filter( 'manage_users_columns',       array( $this, 'user_role_column'        ), 10, 1 );
+		add_filter( 'manage_users_custom_column', array( $this, 'user_role_row'           ), 10, 3 );
+
+		// Only list bbPress roles under Forum Role, remove from WordPress' > 4.4 Site Role list.
+		if ( bbp_get_major_wp_version() >= 4.4 ) {
+			add_filter( 'get_role_list',          array( $this, 'user_role_list_filter'   ), 10, 2 );
+		}
+
+		// User List Table
+		add_action( 'load-users.php',   array( $this, 'user_role_bulk_change' ), 10, 1 );
+		add_action( 'user_row_actions', array( $this, 'user_row_actions'      ), 10, 2 );
+	}
+
+	/**
+	 * Default interface for setting a forum role
+	 *
+	 * @since 2.2.0 bbPress (r4285)
+	 *
+	 * @param WP_User $profileuser User data
+	 * @return bool Always false
+	 */
+	public static function secondary_role_display( $profileuser ) {
+
+		// Bail if current user cannot edit users
+		if ( ! current_user_can( 'edit_user', $profileuser->ID ) ) {
+			return;
+		}
+
+		// Get the roles
+		$dynamic_roles = bbp_get_dynamic_roles();
+
+		// Only keymasters can set other keymasters
+		if ( ! bbp_is_user_keymaster() ) {
+			unset( $dynamic_roles[ bbp_get_keymaster_role() ] );
+		} ?>
+
+		<h2><?php esc_html_e( 'Forums', 'bbpress' ); ?></h2>
+
+		<table class="form-table">
+			<tbody>
+				<tr>
+					<th><label for="bbp-forums-role"><?php esc_html_e( 'Forum Role', 'bbpress' ); ?></label></th>
+					<td>
+
+						<?php $user_role = bbp_get_user_role( $profileuser->ID ); ?>
+
+						<select name="bbp-forums-role" id="bbp-forums-role">
+
+							<?php if ( ! empty( $user_role ) ) : ?>
+
+								<option value=""><?php esc_html_e( '&mdash; No role for these forums &mdash;', 'bbpress' ); ?></option>
+
+							<?php else : ?>
+
+								<option value="" selected="selected"><?php esc_html_e( '&mdash; No role for these forums &mdash;', 'bbpress' ); ?></option>
+
+							<?php endif; ?>
+
+							<?php foreach ( $dynamic_roles as $role => $details ) : ?>
+
+								<option <?php selected( $user_role, $role ); ?> value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+
+							<?php endforeach; ?>
+
+						</select>
+					</td>
+				</tr>
+
+			</tbody>
+		</table>
+
+		<?php
+	}
+
+	/**
+	 * Add bulk forums role dropdown to the WordPress users table
+	 *
+	 * @since 2.2.0 bbPress (r4360)
+	 * @since 2.6.0 bbPress (r6055) Introduced the `$which` parameter.
+	 *
+	 * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
+	 */
+	public static function user_role_bulk_dropdown( $which ) {
+
+		// Remove the bottom list table "change forum role" dropdown from WordPress < 4.6.
+		// See https://bbpress.trac.wordpress.org/ticket/2906.
+		if ( bbp_get_major_wp_version() < 4.6 ) {
+			remove_action( 'restrict_manage_users', array( __CLASS__, 'user_role_bulk_dropdown' ) );
+		}
+
+		// Bail if current user cannot promote users
+		if ( ! current_user_can( 'promote_users' ) ) {
+			return;
+		}
+
+		// Get the roles
+		$dynamic_roles = bbp_get_dynamic_roles();
+
+		// Only keymasters can set other keymasters
+		if ( ! bbp_is_user_keymaster() ) {
+			unset( $dynamic_roles[ bbp_get_keymaster_role() ] );
+		}
+
+		$select_id = 'bottom' === $which ? 'bbp-new-role2' : 'bbp-new-role';
+		$button_id = 'bottom' === $which ? 'bbp-change-role2' : 'bbp-change-role';
+		?>
+
+		<label class="screen-reader-text" for="<?php echo $select_id; ?>"><?php esc_html_e( 'Change forum role to&hellip;', 'bbpress' ) ?></label>
+		<select name="<?php echo $select_id; ?>" id="<?php echo $select_id; ?>" style="display:inline-block; float:none;">
+			<option value=''><?php esc_html_e( 'Change forum role to&hellip;', 'bbpress' ) ?></option>
+			<?php foreach ( $dynamic_roles as $role => $details ) : ?>
+				<option value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+			<?php endforeach; ?>
+		</select><?php submit_button( esc_html__( 'Change', 'bbpress' ), 'secondary', $button_id, false );
+
+		wp_nonce_field( 'bbp-bulk-users', 'bbp-bulk-users-nonce' );
+	}
+
+	/**
+	 * Process bulk dropdown form submission from the WordPress Users
+	 * Table
+	 *
+	 * @since 2.2.0 bbPress (r4365)
+	 *
+	 * @return bool Always false
+	 */
+	public function user_role_bulk_change() {
+
+		// Bail if no users specified
+		if ( empty( $_REQUEST['users'] ) ) {
+			return;
+		}
+
+		// Bail if this isn't a bbPress action
+		if ( ( empty( $_REQUEST['bbp-new-role'] ) && empty( $_REQUEST['bbp-new-role2'] ) ) || ( empty( $_REQUEST['bbp-change-role'] ) && empty( $_REQUEST['bbp-change-role2'] ) ) ) {
+			return;
+		}
+
+		$new_role = false;
+		if ( ! empty( $_REQUEST['bbp-change-role2'] ) && ! empty( $_REQUEST['bbp-new-role2'] ) ) {
+			$new_role = $_REQUEST['bbp-new-role2'];
+		} elseif ( ! empty( $_REQUEST['bbp-change-role'] ) && ! empty( $_REQUEST['bbp-new-role'] ) ) {
+			$new_role = $_REQUEST['bbp-new-role'];
+		}
+
+		// Check that the new role exists
+		$dynamic_roles = bbp_get_dynamic_roles();
+		if ( ! $new_role || empty( $dynamic_roles[ $new_role ] ) ) {
+			return;
+		}
+
+		// Bail if nonce check fails
+		check_admin_referer( 'bbp-bulk-users', 'bbp-bulk-users-nonce' );
+
+		// Bail if current user cannot promote users
+		if ( ! current_user_can( 'promote_users' ) ) {
+			return;
+		}
+
+		// Get the current user ID
+		$current_user_id = (int) bbp_get_current_user_id();
+
+		// Run through user ids
+		foreach ( (array) $_REQUEST['users'] as $user_id ) {
+			$user_id = (int) $user_id;
+
+			// Don't let a user change their own role
+			if ( $user_id === $current_user_id ) {
+				continue;
+			}
+
+			// Set up user and role data
+			$user_role = bbp_get_user_role( $user_id );
+			$new_role  = sanitize_text_field( $new_role );
+
+			// Only keymasters can set other keymasters
+			if ( in_array( bbp_get_keymaster_role(), array( $user_role, $new_role ), true ) && ! bbp_is_user_keymaster() ) {
+				continue;
+			}
+
+			// Set the new forums role
+			if ( $new_role !== $user_role ) {
+				bbp_set_user_role( $user_id, $new_role );
+			}
+		}
+	}
+
+	/**
+	 * Add a "View" link for each user
+	 *
+	 * @since 2.6.0 bbPress (r6502)
+	 *
+	 * @param array   $actions
+	 * @param WP_User $user
+	 *
+	 * @return array Actions with 'view' link added to them
+	 */
+	public function user_row_actions( $actions = array(), $user = false ) {
+
+		// Reverse
+		$actions = array_reverse( $actions );
+
+		// Add the view action link
+		$actions['view'] = '<a href="' . esc_url( bbp_get_user_profile_url( $user->ID ) ) . '" class="bbp-user-profile-link">' . esc_html__( 'View', 'bbpress' ) . '</a>';
+
+		// Re-reverse
+		return array_reverse( $actions );
+	}
+
+	/**
+	 * Add Forum Role column to the WordPress Users table, and change the
+	 * core role title to "Site Role"
+	 *
+	 * @since 2.2.0 bbPress (r4337)
+	 *
+	 * @param array $columns Users table columns
+	 * @return array $columns
+	 */
+	public static function user_role_column( $columns = array() ) {
+
+		// New title for old Role column
+		$columns['role'] = esc_html__( 'Site Role',  'bbpress' );
+
+		// New column
+		$bbp_user_role = array(
+			'bbp_user_role' => esc_html__( 'Forum Role', 'bbpress' )
+		);
+
+		// Make sure role columns are next to each other
+		$role_pos = array_search( 'role', array_keys( $columns ), true );
+		$result   = array_slice( $columns, 0, $role_pos + 1 );
+		$result   = array_merge( $result, $bbp_user_role );
+
+		// Merge and return
+		return array_merge( $result, array_slice( $columns, $role_pos ) );
+	}
+
+	/**
+	 * Return user's forums role for display in the WordPress Users list table
+	 *
+	 * @since 2.2.0 bbPress (r4337)
+	 *
+	 * @param string $retval
+	 * @param string $column_name
+	 * @param int $user_id
+	 *
+	 * @return string Displayable bbPress user role
+	 */
+	public static function user_role_row( $retval = '', $column_name = '', $user_id = 0 ) {
+
+		// User role column
+		if ( 'bbp_user_role' === $column_name ) {
+
+			// Get the users role
+			$user_role = bbp_get_user_role( $user_id );
+			$retval    = false;
+
+			// Translate user role for display
+			if ( ! empty( $user_role ) ) {
+				$roles  = bbp_get_dynamic_roles();
+				$retval = bbp_translate_user_role( $roles[ $user_role ]['name'] );
+			}
+		}
+
+		// Pass retval through
+		return $retval;
+	}
+
+	/**
+	 * Filter the list of roles included in the WordPress site role list
+	 *
+	 * Ensures forum roles are only displayed under the Forum Role list in the
+	 * WordPress Users list table
+	 *
+	 * @since 2.6.0 bbPress (r6051)
+	 *
+	 * @return array $roles
+	 */
+	public static function user_role_list_filter( $roles, $user ) {
+
+		// Get the users role
+		$user_role = bbp_get_user_role( $user->ID );
+
+		if ( ! empty( $user_role ) ) {
+			unset( $roles[ $user_role ] );
+		}
+
+		return $roles;
+	}
+}
+new BBP_Users_Admin();
+endif; // class exists
Index: /tags/2.6.6/src/includes/common/ajax.php
===================================================================
--- /tags/2.6.6/src/includes/common/ajax.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/ajax.php	(revision 7162)
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * bbPress Common AJAX Functions
+ *
+ * Common AJAX functions are ones that are used to setup and/or use during
+ * bbPress specific, theme-side  AJAX requests.
+ *
+ * @package bbPress
+ * @subpackage Ajax
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the URL to use for theme-side bbPress AJAX requests
+ *
+ * @since 2.3.0 bbPress (r4543)
+ */
+function bbp_ajax_url() {
+	echo esc_url( bbp_get_ajax_url() );
+}
+	/**
+	 * Return the URL to use for theme-side bbPress AJAX requests
+	 *
+	 * @since 2.3.0 bbPress (r4543)
+	 *
+	 * @global WP $wp
+	 * @return string
+	 */
+	function bbp_get_ajax_url() {
+		global $wp;
+
+		$ssl      = bbp_get_url_scheme();
+		$url      = trailingslashit( $wp->request );
+		$base_url = home_url( $url, $ssl );
+		$ajaxurl  = add_query_arg( array( 'bbp-ajax' => 'true' ), $base_url );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_ajax_url', $ajaxurl );
+	}
+
+/**
+ * Is this a bbPress AJAX request?
+ *
+ * @since 2.3.0 bbPress (r4543)
+ *
+ * @return bool Looking for bbp-ajax
+ */
+function bbp_is_ajax() {
+	return (bool) ( ( isset( $_GET['bbp-ajax'] ) || isset( $_POST['bbp-ajax'] ) ) && ! empty( $_REQUEST['action'] ) );
+}
+
+/**
+ * Hooked to the 'bbp_template_redirect' action, this is also the custom
+ * theme-side AJAX handler.
+ *
+ * This is largely taken from admin-ajax.php, but adapted specifically for
+ * theme-side bbPress-only AJAX requests.
+ *
+ * @since 2.3.0 bbPress (r4543)
+ *
+ * @param string $action Sanitized action from bbp_post_request/bbp_get_request
+ *
+ * @return If not a bbPress AJAX request
+ */
+function bbp_do_ajax( $action = '' ) {
+
+	// Bail if not a bbPress specific AJAX request
+	if ( ! bbp_is_ajax() ) {
+		return;
+	}
+
+	// Set WordPress core AJAX constant for back-compat
+	if ( ! defined( 'DOING_AJAX' ) ) {
+		define( 'DOING_AJAX', true );
+	}
+
+	// Setup AJAX headers
+	bbp_ajax_headers();
+
+	// Compat for targeted action hooks (without $action param)
+	$action = empty( $action )
+		? sanitize_key( $_REQUEST['action'] ) // isset checked by bbp_is_ajax()
+		: $action;
+
+	// Setup action key
+	$key = "bbp_ajax_{$action}";
+
+	// Bail if no action is registered
+	if ( empty( $action ) || ! has_action( $key ) ) {
+		wp_die( '0', 400 );
+	}
+
+	// Everything is 200 OK.
+	bbp_set_200();
+
+	// Execute custom bbPress AJAX action
+	do_action( $key );
+
+	// All done
+	wp_die( '0' );
+}
+
+/**
+ * Send headers for AJAX specific requests
+ *
+ * This was abstracted from bbp_do_ajax() for use in custom theme-side AJAX
+ * implementations.
+ *
+ * @since 2.6.0 bbPress (r6757)
+ */
+function bbp_ajax_headers() {
+
+	// Set the header content type
+	@header( 'Content-Type: ' . get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ) );
+	@header( 'X-Robots-Tag: noindex' );
+
+	// Disable content sniffing in browsers that support it
+	send_nosniff_header();
+
+	// Disable browser caching for all AJAX requests
+	nocache_headers();
+}
+
+/**
+ * Helper method to return JSON response for bbPress AJAX calls
+ *
+ * @since 2.3.0 bbPress (r4542)
+ *
+ * @param bool $success
+ * @param string $content
+ * @param array $extras
+ */
+function bbp_ajax_response( $success = false, $content = '', $status = -1, $extras = array() ) {
+
+	// Set status to 200 if setting response as successful
+	if ( ( true === $success ) && ( -1 === $status ) ) {
+		$status = 200;
+	}
+
+	// Setup the response array
+	$response = array(
+		'success' => $success,
+		'status'  => $status,
+		'content' => $content
+	);
+
+	// Merge extra response parameters in
+	if ( ! empty( $extras ) && is_array( $extras ) ) {
+		$response = array_merge( $response, $extras );
+	}
+
+	// Send back the JSON
+	@header( 'Content-type: application/json' );
+	echo json_encode( $response );
+	die();
+}
Index: /tags/2.6.6/src/includes/common/classes.php
===================================================================
--- /tags/2.6.6/src/includes/common/classes.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/classes.php	(revision 7162)
@@ -0,0 +1,511 @@
+<?php
+
+/**
+ * bbPress Classes
+ *
+ * @package bbPress
+ * @subpackage Classes
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Component' ) ) :
+/**
+ * bbPress Component Class
+ *
+ * The bbPress component class is responsible for simplifying the creation
+ * of components that share similar behaviors and routines. It is used
+ * internally by bbPress to create forums, topics and replies, but can be
+ * extended to create other really neat things.
+ *
+ * @package bbPress
+ * @subpackage Classes
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+class BBP_Component {
+
+	/**
+	 * @var string Unique name (for internal identification)
+	 * @internal
+	 */
+	var $name;
+
+	/**
+	 * @var Unique ID (normally for custom post type)
+	 */
+	var $id;
+
+	/**
+	 * @var string Unique slug (used in query string and permalinks)
+	 */
+	var $slug;
+
+	/**
+	 * @var WP_Query The loop for this component
+	 */
+	var $query;
+
+	/**
+	 * @var string The current ID of the queried object
+	 */
+	var $current_id;
+
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * bbPress Component loader
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 *
+	 * @param array $args Required. Supports these args:
+	 *  - name: Unique name (for internal identification)
+	 *  - id: Unique ID (normally for custom post type)
+	 *  - slug: Unique slug (used in query string and permalinks)
+	 *  - query: The loop for this component (WP_Query)
+	 *  - current_id: The current ID of the queried object
+	 */
+	public function __construct( $args = array() ) {
+		if ( empty( $args ) ) {
+			return;
+		}
+
+		$this->setup_globals( $args );
+		$this->includes();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Component global variables
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 *
+	 * @access private
+	 */
+	private function setup_globals( $args = array() ) {
+		$this->name = $args['name'];
+		$this->id   = apply_filters( 'bbp_' . $this->name . '_id',   $args['id']   );
+		$this->slug = apply_filters( 'bbp_' . $this->name . '_slug', $args['slug'] );
+	}
+
+	/**
+	 * Include required files
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 *
+	 * @access private
+	 */
+	private function includes() {
+		do_action( 'bbp_' . $this->name . 'includes' );
+	}
+
+	/**
+	 * Setup the actions
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+		add_action( 'bbp_register_post_types',    array( $this, 'register_post_types'    ), 10, 2 ); // Register post types
+		add_action( 'bbp_register_taxonomies',    array( $this, 'register_taxonomies'    ), 10, 2 ); // Register taxonomies
+		add_action( 'bbp_add_rewrite_tags',       array( $this, 'add_rewrite_tags'       ), 10, 2 ); // Add the rewrite tags
+		add_action( 'bbp_generate_rewrite_rules', array( $this, 'generate_rewrite_rules' ), 10, 2 ); // Generate rewrite rules
+
+		// Additional actions can be attached here
+		do_action( 'bbp_' . $this->name . 'setup_actions' );
+	}
+
+	/**
+	 * Setup the component post types
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 */
+	public function register_post_types() {
+		do_action( 'bbp_' . $this->name . '_register_post_types' );
+	}
+
+	/**
+	 * Register component specific taxonomies
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 */
+	public function register_taxonomies() {
+		do_action( 'bbp_' . $this->name . '_register_taxonomies' );
+	}
+
+	/**
+	 * Add any additional rewrite tags
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 */
+	public function add_rewrite_tags() {
+		do_action( 'bbp_' . $this->name . '_add_rewrite_tags' );
+	}
+
+	/**
+	 * Generate any additional rewrite rules
+	 *
+	 * @since 2.0.0 bbPress (r2700)
+	 */
+	public function generate_rewrite_rules( $wp_rewrite ) {
+		do_action_ref_array( 'bbp_' . $this->name . '_generate_rewrite_rules', $wp_rewrite );
+	}
+}
+endif; // BBP_Component
+
+if ( class_exists( 'Walker' ) ) :
+/**
+ * Create HTML dropdown list of bbPress forums/topics.
+ *
+ * @package bbPress
+ * @subpackage Classes
+ *
+ * @since 2.0.0 bbPress (r2746)
+ */
+class BBP_Walker_Dropdown extends Walker {
+
+	/**
+	 * @see Walker::$tree_type
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @var string
+	 */
+	public $tree_type = 'forum';
+
+	/**
+	 * @see Walker::$db_fields
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @var array
+	 */
+	public $db_fields = array(
+		'parent' => 'post_parent',
+		'id'     => 'ID'
+	);
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Set the tree_type
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 */
+	public function __construct() {
+		$this->tree_type = bbp_get_forum_post_type();
+	}
+
+	/**
+	 * @see Walker::start_el()
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @param string $output Passed by reference. Used to append additional
+	 *                       content.
+	 * @param object $object Post data object.
+	 * @param int    $depth  Depth of post in reference to parent posts. Used
+	 *                       for padding.
+	 * @param array  $args   Uses 'selected' argument for selected post to set
+	 *                       selected HTML attribute for option element.
+	 * @param int    $current_object_id
+	 */
+	public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
+		$pad     = str_repeat( '&nbsp;', (int) $depth * 3 );
+		$output .= '<option class="level-' . (int) $depth . '"';
+
+		// Disable the <option> if:
+		// - we're told to do so
+		// - the post type is a forum
+		// - the forum is a category
+		// - forum is closed
+		if (	( true === $args['disable_categories'] )
+				&& ( bbp_get_forum_post_type() === $object->post_type )
+				&& ( bbp_is_forum_category( $object->ID )
+					|| ( ! current_user_can( 'edit_forum', $object->ID ) && bbp_is_forum_closed( $object->ID )
+				)
+			) ) {
+			$output .= ' disabled="disabled" value=""';
+		} else {
+			$output .= ' value="' . (int) $object->ID .'"' . selected( $args['selected'], $object->ID, false );
+		}
+
+		$output .= '>';
+		$title   = apply_filters( 'bbp_walker_dropdown_post_title', $object->post_title, $output, $object, $depth, $args );
+		$output .= $pad . esc_html( $title );
+		$output .= "</option>\n";
+	}
+}
+
+/**
+ * Create hierarchical list of bbPress replies.
+ *
+ * @package bbPress
+ * @subpackage Classes
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+class BBP_Walker_Reply extends Walker {
+
+	/**
+	 * @see Walker::$tree_type
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @var string
+	 */
+	public $tree_type = 'reply';
+
+	/**
+	 * @see Walker::$db_fields
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @var array
+	 */
+	public $db_fields = array(
+		'parent' => 'reply_to',
+		'id'     => 'ID'
+	);
+
+	/**
+	 * Confirm the tree_type
+	 *
+	 * @since 2.6.0 bbPress (r5389)
+	 */
+	public function __construct() {
+		$this->tree_type = bbp_get_reply_post_type();
+	}
+
+	/**
+	 * @see Walker::start_lvl()
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param string $output Passed by reference. Used to append additional content
+	 * @param int $depth Depth of reply
+	 * @param array $args Uses 'style' argument for type of HTML list
+	 */
+	public function start_lvl( &$output = '', $depth = 0, $args = array() ) {
+		bbpress()->reply_query->reply_depth = (int) $depth + 1;
+
+		switch ( $args['style'] ) {
+			case 'div':
+				break;
+			case 'ol':
+				$output .= "<ol class='bbp-threaded-replies'>\n";
+				break;
+			case 'ul':
+			default:
+				$output .= "<ul class='bbp-threaded-replies'>\n";
+				break;
+		}
+	}
+
+	/**
+	 * @see Walker::end_lvl()
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param string $output Passed by reference. Used to append additional content
+	 * @param int $depth Depth of reply
+	 * @param array $args Will only append content if style argument value is 'ol' or 'ul'
+	 */
+	public function end_lvl( &$output = '', $depth = 0, $args = array() ) {
+		bbpress()->reply_query->reply_depth = (int) $depth + 1;
+
+		switch ( $args['style'] ) {
+			case 'div':
+				break;
+			case 'ol':
+				$output .= "</ol>\n";
+				break;
+			case 'ul':
+			default:
+				$output .= "</ul>\n";
+				break;
+		}
+	}
+
+	/**
+	 * @since 2.4.0 bbPress (r4944)
+	 */
+	public function display_element( $element = false, &$children_elements = array(), $max_depth = 0, $depth = 0, $args = array(), &$output = '' ) {
+
+		if ( empty( $element ) ) {
+			return;
+		}
+
+		// Get element's id
+		$id_field = $this->db_fields['id'];
+		$id       = $element->$id_field;
+
+		// Display element
+		parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
+
+		// If we're at the max depth and the current element still has children, loop over those
+		// and display them at this level to prevent them being orphaned to the end of the list.
+		if ( ( $max_depth <= (int) $depth + 1 ) && isset( $children_elements[ $id ] ) ) {
+			foreach ( $children_elements[ $id ] as $child ) {
+				$this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
+			}
+			unset( $children_elements[ $id ] );
+		}
+	}
+
+	/**
+	 * @see Walker:start_el()
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 */
+	public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
+
+		// Set up reply
+		$depth++;
+		bbpress()->reply_query->reply_depth = (int) $depth;
+		bbpress()->reply_query->post        = $object;
+		bbpress()->current_reply_id         = $object->ID;
+
+		// Check for a callback and use it if specified
+		if ( ! empty( $args['callback'] ) ) {
+			ob_start();
+			call_user_func( $args['callback'], $object, $args, $depth );
+			$output .= ob_get_clean();
+			return;
+		}
+
+		// Style for div or list element
+		if ( ! empty( $args['style'] ) && ( 'div' === $args['style'] ) ) {
+			$output .= "<div>\n";
+		} else {
+			$output .= "<li>\n";
+		}
+
+		$output .= bbp_buffer_template_part( 'loop', 'single-reply', false );
+	}
+
+	/**
+	 * @since 2.4.0 bbPress (r4944)
+	 */
+	public function end_el( &$output = '', $object = false, $depth = 0, $args = array() ) {
+
+		// Check for a callback and use it if specified
+		if ( ! empty( $args['end-callback'] ) ) {
+			ob_start();
+			call_user_func( $args['end-callback'], $object, $args, $depth );
+			$output .= ob_get_clean();
+			return;
+		}
+
+		// Style for div or list element
+		if ( ! empty( $args['style'] ) && ( 'div' === $args['style'] ) ) {
+			$output .= "</div>\n";
+		} else {
+			$output .= "</li>\n";
+		}
+	}
+}
+
+/**
+ * Create HTML dropdown list of bbPress replies.
+ *
+ * @package bbPress
+ * @subpackage Classes
+ *
+ * @since 2.6.0 bbPress (r5389)
+ */
+class BBP_Walker_Reply_Dropdown extends Walker {
+
+	/**
+	 * @see Walker::$tree_type
+	 *
+	 * @since 2.6.0 bbPress (r5389)
+	 *
+	 * @var string
+	 */
+	public $tree_type = 'reply';
+
+	/**
+	 * @see Walker::$db_fields
+	 *
+	 * @since 2.6.0 bbPress (r5389)
+	 *
+	 * @var array
+	 */
+	public $db_fields = array(
+		'parent' => 'reply_to',
+		'id'     => 'ID'
+	);
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Confirm the tree_type
+	 *
+	 * @since 2.6.0 bbPress (r5389)
+	 */
+	public function __construct() {
+		$this->tree_type = bbp_get_reply_post_type();
+	}
+
+	/**
+	 * @see Walker::start_el()
+	 *
+	 * @since 2.6.0 bbPress (r5389)
+	 *
+	 * @param string $output Passed by reference. Used to append additional
+	 *                       content.
+	 *
+	 * @param object $object Post data object.
+	 *
+	 * @param int    $depth  Depth of post in reference to parent posts. Used
+	 *                       for padding.
+	 *
+	 * @param array  $args   Uses 'selected' argument for selected post to set
+	 *                       selected HTML attribute for option element.
+	 *
+	 * @param int    $current_object_id Not Used
+	 */
+	public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
+
+		// Set up reply
+		$depth++;
+
+		// Get the reply ID
+		if ( isset( $args['exclude'][0] ) ) {
+			$reply_id = (int) $args['exclude'][0];
+		} else {
+			$reply_id = bbp_get_reply_id();
+		}
+
+		// Get ancestors to determine which items to disable
+		$ancestors = bbp_get_reply_ancestors( $object->ID );
+		array_push( $ancestors, $object->ID );
+
+		// Determine the indentation
+		$pad = str_repeat( '&nbsp;', (int) $depth * 3 );
+
+		// Determine reply title (either post_title, or excerpt of post_content)
+		$title = ! empty( $object->post_title ) ? $object->post_title : wp_html_excerpt( $object->post_content, 10 );
+		$title   = sprintf( esc_html__( '%1$s - %2$s', 'bbpress' ), (int) $object->ID, $title );
+		$title   = apply_filters( 'bbp_walker_dropdown_post_title', $title, $output, $object, $depth, $args );
+
+		// Attributes
+		$class = 'level-' . (int) $depth;
+		$value = (int) $object->ID;
+
+		// Start an output buffer to make late escaping easier
+		ob_start(); ?>
+
+		<option class="<?php echo esc_attr( $class ); ?>" value="<?php echo esc_attr( $value ); ?>"<?php selected( $args['selected'], $object->ID ); ?> <?php disabled( in_array( $reply_id, $ancestors ), true ); ?>><?php echo $pad . esc_html( $title ); ?></option>
+
+		<?php
+
+		// Append the output buffer to the $output variable
+		$output .= ob_get_clean();
+	}
+}
+endif; // class_exists check
Index: /tags/2.6.6/src/includes/common/engagements.php
===================================================================
--- /tags/2.6.6/src/includes/common/engagements.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/engagements.php	(revision 7162)
@@ -0,0 +1,963 @@
+<?php
+
+/**
+ * bbPress Common Engagements
+ *
+ * This file contains the common classes and functions for interacting with the
+ * bbPress engagements API. See `includes/users/engagements.php` for more.
+ *
+ * @package bbPress
+ * @subpackage Common
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return the strategy used for storing user engagements
+ *
+ * @since 2.6.0 bbPress (r6722)
+ *
+ * @param string $rel_key  The key used to index this relationship
+ * @param string $rel_type The type of meta to look in
+ *
+ * @return string
+ */
+function bbp_user_engagements_interface( $rel_key = '', $rel_type = 'post' ) {
+	return apply_filters( 'bbp_user_engagements_interface', bbpress()->engagements, $rel_key, $rel_type );
+}
+
+/**
+ * Base strategy class for interfacing with User Engagements, which other
+ * classes will extend.
+ *
+ * @since 2.6.0 bbPress (r6722)
+ */
+class BBP_User_Engagements_Base {
+
+	/**
+	 * Type of strategy being used.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @var string
+	 */
+	public $type = '';
+
+	/**
+	 * Add a user id to an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 * @param bool   $unique    Whether meta key should be unique to the object
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function add_user_to_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post', $unique = false ) {
+
+	}
+
+	/**
+	 * Remove a user id from an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+	}
+
+	/**
+	 * Remove a user id from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_all_objects( $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+	}
+
+	/**
+	 * Remove an object from all users
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_object_from_all_users( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+	}
+
+	/**
+	 * Remove all users from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_all_users_from_all_objects( $meta_key = '', $meta_type = 'post' ) {
+
+	}
+
+	/**
+	 * Get users of an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param string $meta_key  The key used to index this relationship
+	 * @param string $meta_type The type of meta to look in
+	 *
+	 * @return array Returns ids of users
+	 */
+	public function get_users_for_object( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+	}
+
+	/**
+	 * Get the part of the query responsible for JOINing objects to relationships.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param array  $args
+	 * @param string $meta_key
+	 * @param string $meta_type
+	 *
+	 * @return array
+	 */
+	public function get_query( $args = array(), $context_key = '', $meta_key = '', $meta_type = 'post' ) {
+
+	}
+}
+
+/**
+ * Meta strategy for interfacing with User Engagements
+ *
+ * @since 2.6.0 bbPress (r6722)
+ */
+class BBP_User_Engagements_Meta extends BBP_User_Engagements_Base {
+
+	/**
+	 * Type of strategy being used.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @var string
+	 */
+	public $type = 'meta';
+
+	/**
+	 * Add a user id to an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 * @param bool   $unique    Whether meta key should be unique to the object
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function add_user_to_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post', $unique = false ) {
+		return add_metadata( $meta_type, $object_id, $meta_key, $user_id, $unique );
+	}
+
+	/**
+	 * Remove a user id from an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		return delete_metadata( $meta_type, $object_id, $meta_key, $user_id, false );
+	}
+
+	/**
+	 * Remove a user id from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_all_objects( $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		return delete_metadata( $meta_type, null, $meta_key, $user_id, true );
+	}
+
+	/**
+	 * Remove an object from all users
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_object_from_all_users( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		return delete_metadata( $meta_type, $object_id, $meta_key, null, false );
+	}
+
+	/**
+	 * Remove all users from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_all_users_from_all_objects( $meta_key = '', $meta_type = 'post' ) {
+		return delete_metadata( $meta_type, null, $meta_key, null, true );
+	}
+
+	/**
+	 * Get users of an object
+	 *
+	 * @since 2.6.0 bbPress (r6722)
+	 *
+	 * @param int    $object_id The object id
+	 * @param string $meta_key  The key used to index this relationship
+	 * @param string $meta_type The type of meta to look in
+	 *
+	 * @return array Returns ids of users
+	 */
+	public function get_users_for_object( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		return wp_parse_id_list( get_metadata( $meta_type, $object_id, $meta_key, false ) );
+	}
+
+	/**
+	 * Get the part of the query responsible for JOINing objects to relationships.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param array  $args
+	 * @param string $meta_key
+	 * @param string $meta_type
+	 *
+	 * @return array
+	 */
+	public function get_query( $args = array(), $context_key = '', $meta_key = '', $meta_type = 'post' ) {
+
+		// Backwards compat for pre-2.6.0
+		if ( is_numeric( $args ) ) {
+			$args = array(
+				'meta_query' => array( array(
+					'key'     => $meta_key,
+					'value'   => bbp_get_user_id( $args, false, false ),
+					'compare' => 'NUMERIC'
+				) )
+			);
+		}
+
+		// Default arguments
+		$defaults = array(
+			'meta_query' => array( array(
+				'key'     => $meta_key,
+				'value'   => bbp_get_displayed_user_id(),
+				'compare' => 'NUMERIC'
+			) )
+		);
+
+		// Parse arguments
+		return bbp_parse_args( $args, $defaults, $context_key );
+	}
+}
+
+/**
+ * Term strategy for interfacing with User Engagements
+ *
+ * @since 2.6.0 bbPress (r6737)
+ */
+class BBP_User_Engagements_Term extends BBP_User_Engagements_Base {
+
+	/**
+	 * Type of strategy being used.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @var string
+	 */
+	public $type = 'term';
+
+	/**
+	 * Register an engagement taxonomy just-in-time for immediate use
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param string $tax_key
+	 * @param string $object_type
+	 */
+	private function jit_taxonomy( $tax_key = '', $object_type = 'user' ) {
+
+		// Bail if taxonomy already exists
+		if ( taxonomy_exists( $tax_key ) ) {
+			return;
+		}
+
+		// Register the taxonomy
+		register_taxonomy( $tax_key, 'bbp_' . $object_type, array(
+			'labels'                => array(),
+			'description'           => '',
+			'public'                => false,
+			'publicly_queryable'    => false,
+			'hierarchical'          => false,
+			'show_ui'               => false,
+			'show_in_menu'          => false,
+			'show_in_nav_menus'     => false,
+			'show_tagcloud'         => false,
+			'show_in_quick_edit'    => false,
+			'show_admin_column'     => false,
+			'meta_box_cb'           => false,
+			'capabilities'          => array(),
+			'rewrite'               => false,
+			'query_var'             => '',
+			'update_count_callback' => '',
+			'show_in_rest'          => false,
+			'rest_base'             => false,
+			'rest_controller_class' => false,
+			'_builtin'              => false
+		) );
+	}
+
+	/**
+	 * Add a user id to an object
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 * @param bool   $unique    Whether meta key should be unique to the object
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function add_user_to_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post', $unique = false ) {
+		$user_key = "{$meta_key}_user_id_{$user_id}";
+		$tax_key  = "{$meta_key}_{$meta_type}";
+		$this->jit_taxonomy( $tax_key );
+
+		return wp_add_object_terms( $object_id, $user_key, $tax_key );
+	}
+
+	/**
+	 * Remove a user id from an object
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		$user_key = "{$meta_key}_user_id_{$user_id}";
+		$tax_key  = "{$meta_key}_{$meta_type}";
+		$this->jit_taxonomy( $tax_key );
+
+		return wp_remove_object_terms( $object_id, $user_key, $tax_key );
+	}
+
+	/**
+	 * Remove a user id from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_all_objects( $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		$user_key = "{$meta_key}_user_id_{$user_id}";
+		$tax_key  = "{$meta_key}_{$meta_type}";
+		$this->jit_taxonomy( $tax_key );
+		$term     = get_term_by( 'slug', $user_key, $tax_key );
+
+		return wp_delete_term( $term->term_id, $tax_key );
+	}
+
+	/**
+	 * Remove an object from all users
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_object_from_all_users( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		return wp_delete_object_term_relationships( $object_id, get_object_taxonomies( 'bbp_user' ) );
+	}
+
+	/**
+	 * Remove all users from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_all_users_from_all_objects( $meta_key = '', $meta_type = 'post' ) {
+		// TODO
+	}
+
+	/**
+	 * Get users of an object
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param int    $object_id The object id
+	 * @param string $meta_key  The key used to index this relationship
+	 * @param string $meta_type The type of meta to look in
+	 *
+	 * @return array Returns ids of users
+	 */
+	public function get_users_for_object( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		$user_key = "{$meta_key}_user_id_";
+		$tax_key  = "{$meta_key}_{$meta_type}";
+		$this->jit_taxonomy( $tax_key );
+
+		// Get terms
+		$terms = get_terms( array(
+			'object_ids' => $object_id,
+			'taxonomy'   => $tax_key
+		) );
+
+		// Slug part to replace
+		$user_ids = array();
+
+		// Loop through terms and get the user ID
+		foreach ( $terms as $term ) {
+			$user_ids[] = str_replace( $user_key, '', $term->slug );
+		}
+
+		// Parse & return
+		return wp_parse_id_list( $user_ids );
+	}
+
+	/**
+	 * Get the part of the query responsible for JOINing objects to relationships.
+	 *
+	 * @since 2.6.0 bbPress (r6737)
+	 *
+	 * @param array  $args
+	 * @param string $meta_key
+	 * @param string $meta_type
+	 *
+	 * @return array
+	 */
+	public function get_query( $args = array(), $context_key = '', $meta_key = '', $meta_type = 'post' ) {
+		$tax_key  = "{$meta_key}_{$meta_type}";
+		$user_key = "{$meta_key}_user_id_";
+
+		// Make sure the taxonomy is registered
+		$this->jit_taxonomy( $tax_key );
+
+		// Backwards compat for pre-2.6.0
+		if ( is_numeric( $args ) ) {
+			$args = array(
+				'tax_query' => array( array(
+					'taxonomy' => $tax_key,
+					'terms'    => $user_key . bbp_get_user_id( $args, false, false ),
+					'field'    => 'slug'
+				) )
+			);
+		}
+
+		// Default arguments
+		$defaults = array(
+			'tax_query' => array( array(
+				'taxonomy' => $tax_key,
+				'terms'    => $user_key . bbp_get_displayed_user_id(),
+				'field'    => 'slug'
+			) )
+		);
+
+		// Parse arguments
+		return bbp_parse_args( $args, $defaults, $context_key );
+	}
+}
+
+/**
+ * User strategy for interfacing with User Engagements
+ *
+ * This strategy largely exists for backwards compatibility with bbPress 2.5,
+ * or installations that have not upgraded their databases to 2.6 or above.
+ *
+ * Note: this strategy is going to be a bit less tidy than the others, because
+ * it needs to do weird things to maintain the 2.5 status-quo. Do not use this
+ * strategy as an example when building your own.
+ *
+ * @since 2.6.0 bbPress (r6844)
+ */
+class BBP_User_Engagements_User extends BBP_User_Engagements_Base {
+
+	/**
+	 * Type of strategy being used.
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @var string
+	 */
+	public $type = 'user';
+
+	/**
+	 * Private function to map 2.6 meta keys to 2.5 user-option keys.
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param string $meta_key
+	 * @param int    $object_id
+	 * @param bool   $prefix
+	 *
+	 * @return string
+	 */
+	private function get_user_option_key( $meta_key = '', $object_id = 0, $prefix = false ) {
+		switch ( $meta_key ) {
+
+			// Favorites
+			case '_bbp_favorite' :
+				$key = '_bbp_favorites';
+				break;
+
+			// Subscriptions
+			case '_bbp_subscription' :
+
+				// Maybe guess at post type
+				$post_type = ! empty( $object_id )
+					? get_post_type( $object_id )
+					: bbp_get_topic_post_type();
+
+				// Forums & Topics used different keys :/
+				$key = ( bbp_get_forum_post_type() === $post_type )
+					? '_bbp_forum_subscriptions'
+					: '_bbp_subscriptions';
+
+				break;
+
+			// Unknown, so pluralize
+			default :
+				$key = "{$meta_key}s";
+				break;
+		}
+
+		// Maybe prefix the key (for use in raw database queries)
+		if ( true === $prefix ) {
+			$key = bbp_db()->get_blog_prefix() . $key;
+		}
+
+		// Return the old (pluralized) user option key
+		return $key;
+	}
+
+	/**
+	 * Private function to get a 2.5 compatible cache key.
+	 *
+	 * This method exists to provide backwards compatibility with bbPress 2.5,
+	 * which had caching surrounding the FIND_IN_SET usermeta queries.
+	 *
+	 * @since 2.6.3 bbPress (r6991)
+	 *
+	 * @param string $meta_key
+	 * @param int    $object_id
+	 *
+	 * @return string
+	 */
+	private function get_cache_key( $meta_key = '', $object_id = 0 ) {
+
+		// No negative numbers in cache keys (zero is weird, but not disallowed)
+		$object_id = absint( $object_id );
+
+		// Maybe guess at post type
+		$post_type = ! empty( $object_id )
+			? get_post_type( $object_id )
+			: bbp_get_topic_post_type();
+
+		switch ( $meta_key ) {
+
+			// Favorites
+			case '_bbp_favorite' :
+				$key = 'bbp_get_topic_favoriters_';
+				break;
+
+			// Subscriptions
+			case '_bbp_subscription' :
+
+				// Forums & Topics used different keys :/
+				$key = ( bbp_get_forum_post_type() === $post_type )
+					? 'bbp_get_forum_subscribers_'
+					: 'bbp_get_topic_subscribers_';
+
+				break;
+
+			// Unknown, so pluralize
+			default :
+				$nounize = rtrim( $meta_key, 'e' );
+				$key     = "bbp_get_{$post_type}_{$nounize}ers_";
+				break;
+		}
+
+		// Return the old (pluralized) user option key with object ID appended
+		return "{$key}{$object_id}";
+	}
+
+	/**
+	 * Get the user engagement cache for a given meta key and object ID.
+	 *
+	 * This method exists to provide backwards compatibility with bbPress 2.5,
+	 * which had caching surrounding the FIND_IN_SET queries in usermeta.
+	 *
+	 * @since 2.6.3 bbPress (r6991)
+	 *
+	 * @param string $meta_key
+	 * @param int    $object_id
+	 *
+	 * @return mixed Results from cache get
+	 */
+	private function cache_get( $meta_key = '', $object_id = 0 ) {
+		$cache_key = $this->get_cache_key( $meta_key, $object_id );
+
+		return wp_cache_get( $cache_key, 'bbpress_engagements' );
+	}
+
+	/**
+	 * Set the user engagement cache for a given meta key and object ID.
+	 *
+	 * This method exists to provide backwards compatibility with bbPress 2.5,
+	 * which had caching surrounding the FIND_IN_SET queries in usermeta.
+	 *
+	 * @since 2.6.3 bbPress (r6991)
+	 *
+	 * @param string $meta_key
+	 * @param int    $object_id
+	 *
+	 * @return mixed Results from cache set
+	 */
+	private function cache_set( $meta_key = '', $object_id = 0, $user_ids = array() ) {
+		$cache_key = $this->get_cache_key( $meta_key, $object_id );
+		$user_ids  = $this->parse_comma_list( $user_ids );
+
+		return wp_cache_set( $cache_key, $user_ids, 'bbpress_engagements' );
+	}
+
+	/**
+	 * Delete the user engagement cache for a given meta key and object ID.
+	 *
+	 * This method exists to provide backwards compatibility with bbPress 2.5,
+	 * which had caching surrounding the FIND_IN_SET queries in usermeta.
+	 *
+	 * @since 2.6.3 bbPress (r6991)
+	 *
+	 * @param string $meta_key
+	 * @param int    $object_id
+	 *
+	 * @return mixed Results from cache delete
+	 */
+	private function cache_delete( $meta_key = '', $object_id = 0 ) {
+		$cache_key = $this->get_cache_key( $meta_key, $object_id );
+
+		return wp_cache_delete( $cache_key, 'bbpress_engagements' );
+	}
+
+	/**
+	 * Turn a comma-separated string into an array of integers
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param string $results
+	 * @return array
+	 */
+	private function parse_comma_list( $results = '' ) {
+		return array_filter( wp_parse_id_list( $results ) );
+	}
+
+	/**
+	 * Add a user id to an object
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 * @param bool   $unique    Whether meta key should be unique to the object
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function add_user_to_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post', $unique = false ) {
+		$retval     = false;
+		$option_key = $this->get_user_option_key( $meta_key, $object_id );
+		$object_ids = $this->parse_comma_list( get_user_option( $option_key, $user_id ) );
+		$exists     = array_search( $object_id, $object_ids );
+
+		// Not already added, so add it
+		if ( false === $exists ) {
+			$object_ids[] = $object_id;
+			$object_ids   = implode( ',', $this->parse_comma_list( $object_ids ) );
+			$retval       = update_user_option( $user_id, $option_key, $object_ids );
+
+			// Delete cache if successful (accounts for int & true)
+			if ( false !== $retval ) {
+				$this->cache_delete( $meta_key, $object_id );
+			}
+		}
+
+		// Return true if added, or false if not
+		return $retval;
+	}
+
+	/**
+	 * Remove a user id from an object
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_object( $object_id = 0, $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+		$retval     = false;
+		$option_key = $this->get_user_option_key( $meta_key, $object_id );
+		$object_ids = $this->parse_comma_list( get_user_option( $option_key, $user_id ) );
+		$exists     = array_search( $object_id, $object_ids );
+
+		// Exists, so remove it
+		if ( false !== $exists ) {
+			unset( $object_ids[ $exists ] );
+
+			$object_ids = implode( ',', $this->parse_comma_list( $object_ids ) );
+			$retval     = ! empty( $object_ids )
+				? update_user_option( $user_id, $option_key, $object_ids )
+				: delete_user_option( $user_id, $option_key );
+
+			// Delete cache if successful (accounts for int & true)
+			if ( false !== $retval ) {
+				$this->cache_delete( $meta_key, $object_id );
+			}
+		}
+
+		// Return true if removed, or false if not
+		return $retval;
+	}
+
+	/**
+	 * Remove a user id from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_user_from_all_objects( $user_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+		// Get the key
+		$option_key = $this->get_user_option_key( $meta_key );
+
+		// Get the option
+		$object_ids = $this->parse_comma_list( get_user_option( $option_key, $user_id ) );
+
+		// Attempt to delete the user option
+		$retval = delete_user_option( $user_id, $option_key );
+
+		// Try to delete caches, but only if everything else succeeded
+		if ( ! empty( $retval ) && ! empty( $object_ids ) ) {
+			foreach ( $object_ids as $object_id ) {
+				$this->cache_delete( $meta_key, $object_id );
+			}
+		}
+
+		// Return true if user was removed, or false if not
+		return $retval;
+	}
+
+	/**
+	 * Remove an object from all users
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param int    $object_id The object id
+	 * @param int    $user_id   The user id
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_object_from_all_users( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+		// Query for users
+		$user_ids = $this->get_users_for_object( $object_id, $meta_key, $meta_type );
+		$u_count  = count( $user_ids );
+
+		// Count number of removals
+		$removed  = array();
+		$r_count  = 0;
+
+		// Users have engaged, so remove them
+		if ( ! empty( $u_count ) ) {
+
+			// Loop through users and remove them from the object
+			foreach ( $user_ids as $user_id ) {
+				$removed[] = $this->remove_user_from_object( $object_id, $user_id, $meta_key, $meta_type );
+			}
+
+			// Count the removed users
+			$r_count = count( $removed );
+		}
+
+		// Return true if successfully removed from all users
+		return ( $r_count === $u_count );
+	}
+
+	/**
+	 * Remove all users from all objects
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param string $meta_key  The relationship key
+	 * @param string $meta_type The relationship type (usually 'post')
+	 *
+	 * @return bool Returns true on success, false on failure
+	 */
+	public function remove_all_users_from_all_objects( $meta_key = '', $meta_type = 'post' ) {
+
+		// Query for users
+		$option_key = $this->get_user_option_key( $meta_key, 0, true );
+		$bbp_db     = bbp_db();
+		$user_ids   = $bbp_db->get_col( "SELECT user_id FROM {$bbp_db->usermeta} WHERE meta_key = '{$option_key}'" );
+		$u_count    = count( $user_ids );
+
+		// Count number of removals
+		$removed    = array();
+		$r_count    = 0;
+
+		// Users have engaged, so remove them
+		if ( ! empty( $u_count ) ) {
+
+			// Loop through users and remove their user options
+			foreach ( $user_ids as $user_id ) {
+				$removed[] = $this->remove_user_from_all_objects( $user_id, $meta_key );
+			}
+
+			// Count the removed users
+			$r_count = count( $removed );
+		}
+
+		// Return true if successfully removed from all users
+		return ( $r_count === $u_count );
+	}
+
+	/**
+	 * Get users of an object
+	 *
+	 * The database queries in this function were cached in bbPress versions
+	 * older than 2.6, but no longer are to avoid cache pollution.
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param int    $object_id The object id
+	 * @param string $meta_key  The key used to index this relationship
+	 * @param string $meta_type The type of meta to look in
+	 *
+	 * @return array Returns ids of users
+	 */
+	public function get_users_for_object( $object_id = 0, $meta_key = '', $meta_type = 'post' ) {
+
+		// Try to get user IDs from cache
+		$user_ids = $this->cache_get( $meta_key, $object_id );
+
+		// Cache is empty, so hit the database
+		if ( false === $user_ids ) {
+			$option_key = $this->get_user_option_key( $meta_key, $object_id, true );
+			$bbp_db     = bbp_db();
+			$user_ids   = $bbp_db->get_col( "SELECT user_id FROM {$bbp_db->usermeta} WHERE meta_key = '{$option_key}' and FIND_IN_SET('{$object_id}', meta_value) > 0" );
+
+			// Always cache results (even if empty, to prevent multiple misses)
+			$this->cache_set( $meta_key, $object_id, $user_ids );
+		}
+
+		// Return parsed IDs
+		return $this->parse_comma_list( $user_ids );
+	}
+
+	/**
+	 * Get the part of the query responsible for JOINing objects to relationships.
+	 *
+	 * @since 2.6.0 bbPress (r6844)
+	 *
+	 * @param array  $args
+	 * @param string $meta_key
+	 * @param string $meta_type
+	 *
+	 * @return array
+	 */
+	public function get_query( $args = array(), $context_key = '', $meta_key = '', $meta_type = 'post' ) {
+		$user_id    = bbp_get_user_id( $args, true, true );
+		$option_key = $this->get_user_option_key( $meta_key );
+		$object_ids = $this->parse_comma_list( get_user_option( $option_key, $user_id ) );
+
+		// Maybe trick WP_Query into ".ID IN (0)" to return no results
+		if ( empty( $object_ids ) ) {
+			$object_ids = array( 0 );
+		}
+
+		// Maybe include these post IDs
+		$args = array(
+			'post__in' => $object_ids
+		);
+
+		// Parse arguments
+		return bbp_parse_args( $args, array(), $context_key );
+	}
+}
Index: /tags/2.6.6/src/includes/common/formatting.php
===================================================================
--- /tags/2.6.6/src/includes/common/formatting.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/formatting.php	(revision 7162)
@@ -0,0 +1,784 @@
+<?php
+
+/**
+ * bbPress Formatting
+ *
+ * @package bbPress
+ * @subpackage Formatting
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Kses **********************************************************************/
+
+/**
+ * Custom allowed tags for forum topics and replies
+ *
+ * Allows all users to post links, quotes, code, formatting, lists, and images
+ *
+ * @since 2.3.0 bbPress (r4603)
+ *
+ * @return array Associative array of allowed tags and attributes
+ */
+function bbp_kses_allowed_tags() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_kses_allowed_tags', array(
+
+		// Links
+		'a' => array(
+			'href'     => true,
+			'title'    => true,
+			'rel'      => true,
+			'target'   => true
+		),
+
+		// Quotes
+		'blockquote'   => array(
+			'cite'     => true
+		),
+
+		// Code
+		'code'         => array(),
+		'pre'          => array(
+			'class'    => true
+		),
+
+		// Formatting
+		'em'           => array(),
+		'strong'       => array(),
+		'del'          => array(
+			'datetime' => true,
+			'cite'     => true
+		),
+		'ins' => array(
+			'datetime' => true,
+			'cite'     => true
+		),
+
+		// Lists
+		'ul'           => array(),
+		'ol'           => array(
+			'start'    => true,
+		),
+		'li'           => array(),
+
+		// Images
+		'img'          => array(
+			'src'      => true,
+			'border'   => true,
+			'alt'      => true,
+			'height'   => true,
+			'width'    => true,
+		)
+	) );
+}
+
+/**
+ * Custom kses filter for forum topics and replies, for filtering incoming data
+ *
+ * @since 2.3.0 bbPress (r4603)
+ *
+ * @param string $data Content to filter, expected to be escaped with slashes
+ * @return string Filtered content
+ */
+function bbp_filter_kses( $data = '' ) {
+	return wp_slash( wp_kses( wp_unslash( $data ), bbp_kses_allowed_tags() ) );
+}
+
+/**
+ * Custom kses filter for forum topics and replies, for raw data
+ *
+ * @since 2.3.0 bbPress (r4603)
+ *
+ * @param string $data Content to filter, expected to not be escaped
+ * @return string Filtered content
+ */
+function bbp_kses_data( $data = '' ) {
+	return wp_kses( $data , bbp_kses_allowed_tags() );
+}
+
+/** Formatting ****************************************************************/
+
+/**
+ * Filter the topic or reply content and output code and pre tags
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @param string $content Topic and reply content
+ * @return string Partially encoded content
+ */
+function bbp_code_trick( $content = '' ) {
+	$content = str_replace( array( "\r\n", "\r" ), "\n", $content );
+	$content = preg_replace_callback( "|(`)(.*?)`|",      'bbp_encode_callback', $content );
+	$content = preg_replace_callback( "!(^|\n)`(.*?)`!s", 'bbp_encode_callback', $content );
+
+	return $content;
+}
+
+/**
+ * When editing a topic or reply, reverse the code trick so the textarea
+ * contains the correct editable content.
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @param string $content Topic and reply content
+ * @return string Partially encoded content
+ */
+function bbp_code_trick_reverse( $content = '' ) {
+
+	// Setup variables
+	$openers = array( '<p>', '<br />' );
+	$content = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", 'bbp_decode_callback', $content );
+
+	// Do the do
+	$content = str_replace( $openers,       '',       $content );
+	$content = str_replace( '</p>',         "\n",     $content );
+	$content = str_replace( '<coded_br />', '<br />', $content );
+	$content = str_replace( '<coded_p>',    '<p>',    $content );
+	$content = str_replace( '</coded_p>',   '</p>',   $content );
+
+	return $content;
+}
+
+/**
+ * Filter the content and encode any bad HTML tags
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @param string $content Topic and reply content
+ * @return string Partially encoded content
+ */
+function bbp_encode_bad( $content = '' ) {
+
+	// Setup variables
+	$content = _wp_specialchars( $content, ENT_NOQUOTES );
+	$content = preg_split( '@(`[^`]*`)@m', $content, -1, PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE );
+	$allowed = bbp_kses_allowed_tags();
+	$empty   = array(
+		'br'    => true,
+		'hr'    => true,
+		'img'   => true,
+		'input' => true,
+		'param' => true,
+		'area'  => true,
+		'col'   => true,
+		'embed' => true
+	);
+
+	// Loop through allowed tags and compare for empty and normal tags
+	foreach ( $allowed as $tag => $args ) {
+		$preg = $args ? "{$tag}(?:\s.*?)?" : $tag;
+
+		// Which walker to use based on the tag and arguments
+		if ( isset( $empty[ $tag ] ) ) {
+			array_walk( $content, 'bbp_encode_empty_callback',  $preg );
+		} else {
+			array_walk( $content, 'bbp_encode_normal_callback', $preg );
+		}
+	}
+
+	// Return the joined content array
+	return implode( '', $content );
+}
+
+/** Code Callbacks ************************************************************/
+
+/**
+ * Callback to encode the tags in topic or reply content
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @param array $matches
+ * @return string
+ */
+function bbp_encode_callback( $matches = array() ) {
+
+	// Trim inline code, not pre blocks (to prevent removing indentation)
+	if ( "`" === $matches[1] ) {
+		$content = trim( $matches[2] );
+	} else {
+		$content = $matches[2];
+	}
+
+	// Do some replacing
+	$content = htmlspecialchars( $content, ENT_QUOTES );
+	$content = str_replace( array( "\r\n", "\r" ), "\n", $content );
+	$content = preg_replace( "|\n\n\n+|", "\n\n", $content );
+	$content = str_replace( '&amp;amp;', '&amp;', $content );
+	$content = str_replace( '&amp;lt;',  '&lt;',  $content );
+	$content = str_replace( '&amp;gt;',  '&gt;',  $content );
+
+	// Wrap in code tags
+	$content = '<code>' . $content . '</code>';
+
+	// Wrap blocks in pre tags
+	if ( "`" !== $matches[1] ) {
+		$content = "\n<pre>" . $content . "</pre>\n";
+	}
+
+	return $content;
+}
+
+/**
+ * Callback to decode the tags in topic or reply content
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @param array $matches
+ * @todo Experiment with _wp_specialchars()
+ * @return string
+ */
+function bbp_decode_callback( $matches = array() ) {
+
+	// Setup variables
+	$trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) );
+	$amps        = array( '&#38;','&#038;', '&amp;' );
+	$single      = array( '&#39;','&#039;'          );
+	$content     = $matches[2];
+	$content     = strtr( $content, $trans_table );
+
+	// Do the do
+	$content = str_replace( '<br />', '<coded_br />', $content );
+	$content = str_replace( '<p>',    '<coded_p>',    $content );
+	$content = str_replace( '</p>',   '</coded_p>',   $content );
+	$content = str_replace( $amps,    '&',            $content );
+	$content = str_replace( $single,  "'",            $content );
+
+	// Return content wrapped in code tags
+	return '`' . $content . '`';
+}
+
+/**
+ * Callback to replace empty HTML tags in a content string
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @internal Used by bbp_encode_bad()
+ * @param string $content
+ * @param string $key Not used
+ * @param string $preg
+ */
+function bbp_encode_empty_callback( &$content = '', $key = '', $preg = '' ) {
+	if ( strpos( $content, '`' ) !== 0 ) {
+		$content = preg_replace( "|&lt;({$preg})\s*?/*?&gt;|i", '<$1 />', $content );
+	}
+}
+
+/**
+ * Callback to replace normal HTML tags in a content string
+ *
+ * @since 2.3.0 bbPress (r4641)
+ *
+ * @internal Used by bbp_encode_bad()
+ *
+ * @param string $content
+ * @param string $key
+ * @param string $preg
+ */
+function bbp_encode_normal_callback( &$content = '', $key = '', $preg = '') {
+	if ( strpos( $content, '`' ) !== 0 ) {
+		$content = preg_replace( "|&lt;(/?{$preg})&gt;|i", '<$1>', $content );
+	}
+}
+
+/** No Follow *****************************************************************/
+
+/**
+ * Catches links so rel=nofollow can be added (on output, not save)
+ *
+ * @since 2.3.0 bbPress (r4865)
+ *
+ * @param string $text Post text
+ * @return string $text Text with rel=nofollow added to any links
+ */
+function bbp_rel_nofollow( $text = '' ) {
+	return preg_replace_callback( '|<a (.+?)>|i', 'bbp_rel_nofollow_callback', $text );
+}
+
+/**
+ * Adds rel=nofollow to a link
+ *
+ * @since 2.3.0 bbPress (r4865)
+ *
+ * @param array $matches
+ * @return string $text Link with rel=nofollow added
+ */
+function bbp_rel_nofollow_callback( $matches = array() ) {
+	$text     = $matches[1];
+	$atts     = shortcode_parse_atts( $matches[1] );
+	$rel      = 'nofollow';
+	$home_url = home_url();
+
+	// Bail on links that match the current domain
+	if ( preg_match( '%href=["\'](' . preg_quote( set_url_scheme( $home_url, 'http'  ) ) . ')%i', $text ) ||
+	     preg_match( '%href=["\'](' . preg_quote( set_url_scheme( $home_url, 'https' ) ) . ')%i', $text )
+	) {
+		return "<a {$text}>";
+	}
+
+	// Avoid collisions with existing "rel" attribute
+	if ( ! empty( $atts['rel'] ) ) {
+		$parts = array_map( 'trim', explode( ' ', $atts['rel'] ) );
+		if ( false === array_search( 'nofollow', $parts ) ) {
+			$parts[] = 'nofollow';
+		}
+
+		$rel = implode( ' ', $parts );
+		unset( $atts['rel'] );
+
+		$html = '';
+		foreach ( $atts as $name => $value ) {
+			$html .= "{$name}=\"{$value}\" ";
+		}
+
+		$text = trim( $html );
+	}
+
+	return "<a {$text} rel=\"{$rel}\">";
+}
+
+/** Make Clickable ************************************************************/
+
+/**
+ * Convert plaintext URI to HTML links.
+ *
+ * Converts URI, www and ftp, and email addresses. Finishes by fixing links
+ * within links.
+ *
+ * This custom version of WordPress's make_clickable() skips links inside of
+ * pre and code tags.
+ *
+ * @since 2.4.0 bbPress (r4941)
+ *
+ * @param string $text Content to convert URIs.
+ * @return string Content with converted URIs.
+ */
+function bbp_make_clickable( $text = '' ) {
+	$r               = '';
+	$textarr         = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
+	$nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
+
+	foreach ( $textarr as $piece ) {
+
+		if ( preg_match( '|^<code[\s>]|i', $piece ) || preg_match( '|^<pre[\s>]|i', $piece ) || preg_match( '|^<script[\s>]|i', $piece ) || preg_match( '|^<style[\s>]|i', $piece ) ) {
+			$nested_code_pre++;
+		} elseif ( $nested_code_pre && ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) || '</script>' === strtolower( $piece ) || '</style>' === strtolower( $piece ) ) ) {
+			$nested_code_pre--;
+		}
+
+		if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) {
+			$r .= $piece;
+			continue;
+		}
+
+		// Long strings might contain expensive edge cases ...
+		if ( 10000 < strlen( $piece ) ) {
+			// ... break it up
+			foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
+				if ( 2101 < strlen( $chunk ) ) {
+					$r .= $chunk; // Too big, no whitespace: bail.
+				} else {
+					$r .= bbp_make_clickable( $chunk );
+				}
+			}
+		} else {
+			$ret = " {$piece} "; // Pad with whitespace to simplify the regexes
+			$ret = apply_filters( 'bbp_make_clickable', $ret, $text );
+			$ret = substr( $ret, 1, -1 ); // Remove our whitespace padding.
+			$r .= $ret;
+		}
+	}
+
+	// Cleanup of accidental links within links
+	return preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a>([^<]*)</a>#i', "$1$3$4</a>", $r );
+}
+
+/**
+ * Make URLs clickable in content areas
+ *
+ * @since 2.6.0 bbPress (r6014)
+ *
+ * @param  string $text
+ * @return string
+ */
+function bbp_make_urls_clickable( $text = '' ) {
+	$url_clickable = '~
+		([\\s(<.,;:!?])                                # 1: Leading whitespace, or punctuation
+		(                                              # 2: URL
+			[\\w]{1,20}+://                            # Scheme and hier-part prefix
+			(?=\S{1,2000}\s)                           # Limit to URLs less than about 2000 characters long
+			[\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+     # Non-punctuation URL character
+			(?:                                        # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
+				[\'.,;:!?)]                            # Punctuation URL character
+				[\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
+			)*
+		)
+		(\)?)                                          # 3: Trailing closing parenthesis (for parethesis balancing post processing)
+	~xS';
+
+	// The regex is a non-anchored pattern and does not have a single fixed starting character.
+	// Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
+	return preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $text );
+}
+
+/**
+ * Make FTP clickable in content areas
+ *
+ * @since 2.6.0 bbPress (r6014)
+ *
+ * @see make_clickable()
+ *
+ * @param  string $text
+ * @return string
+ */
+function bbp_make_ftps_clickable( $text = '' ) {
+	return preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $text );
+}
+
+/**
+ * Make emails clickable in content areas
+ *
+ * @since 2.6.0 bbPress (r6014)
+ *
+ * @see make_clickable()
+ *
+ * @param  string $text
+ * @return string
+ */
+function bbp_make_emails_clickable( $text = '' ) {
+	return preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $text );
+}
+
+/**
+ * Make mentions clickable in content areas
+ *
+ * @since 2.6.0 bbPress (r6014)
+ *
+ * @see make_clickable()
+ *
+ * @param  string $text
+ * @return string
+ */
+function bbp_make_mentions_clickable( $text = '' ) {
+	return preg_replace_callback( '#([\s>])@([0-9a-zA-Z-_]+)#i', 'bbp_make_mentions_clickable_callback', $text );
+}
+
+/**
+ * Callback to convert mention matches to HTML A tag.
+ *
+ * @since 2.6.0 bbPress (r6014)
+ *
+ * @param array $matches Regular expression matches in the current text blob.
+ *
+ * @return string Original text if no user exists, or link to user profile.
+ */
+function bbp_make_mentions_clickable_callback( $matches = array() ) {
+
+	// Bail if the match is empty malformed
+	if ( empty( $matches[2] ) || ! is_string( $matches[2] ) ) {
+		return $matches[0];
+	}
+
+	// Get user; bail if not found
+	$user = get_user_by( 'slug', $matches[2] );
+	if ( empty( $user ) || bbp_is_user_inactive( $user->ID ) ) {
+		return $matches[0];
+	}
+
+	// Default anchor classes
+	$classes = array(
+		'bbp-user-mention',
+		'bbp-user-id-' . absint( $user->ID )
+	);
+
+	// Filter classes
+	$classes = (array) apply_filters( 'bbp_make_mentions_clickable_classes', $classes, $user );
+
+	// Escape & implode if not empty, otherwise an empty string
+	$class_str = ! empty( $classes )
+		? implode( ' ', array_map( 'sanitize_html_class', $classes ) )
+		: '';
+
+	// Setup as a variable to avoid a potentially empty class attribute
+	$class = ! empty( $class_str )
+		? ' class="' . esc_attr( $class_str ) . '"'
+		: '';
+
+	// Create the link to the user's profile
+	$html   = '<a href="%1$s"' . $class . '>%2$s</a>';
+	$url    = bbp_get_user_profile_url( $user->ID );
+	$anchor = sprintf( $html, esc_url( $url ), esc_html( $matches[0] ) );
+
+	// Prevent this link from being followed by bots
+	$link   = bbp_rel_nofollow( $anchor );
+
+	// Concatenate the matches into the return value
+	$retval = $matches[1] . $link;
+
+	// Return the link
+	return $retval;
+}
+
+/** Numbers *******************************************************************/
+
+/**
+ * Never let a numeric value be less than zero.
+ *
+ * @since 2.6.0 bbPress (r6300)
+ *
+ * @param int $number
+ */
+function bbp_number_not_negative( $number = 0 ) {
+
+	// Protect against formatted strings
+	if ( is_string( $number ) ) {
+		$number = strip_tags( $number );                    // No HTML
+		$number = preg_replace( '/[^0-9-]/', '', $number ); // No number-format
+
+	// Protect against objects, arrays, scalars, etc...
+	} elseif ( ! is_numeric( $number ) ) {
+		$number = 0;
+	}
+
+	// Make the number an integer
+	$int = intval( $number );
+
+	// Pick the maximum value, never less than zero
+	$not_less_than_zero = max( 0, $int );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_number_not_negative', $not_less_than_zero, $int, $number );
+}
+
+/**
+ * A bbPress specific method of formatting numeric values
+ *
+ * @since 2.0.0 bbPress (r2486)
+ *
+ * @param string $number Number to format
+ * @param string $decimals Optional. Display decimals
+ *
+ * @return string Formatted string
+ */
+function bbp_number_format( $number = 0, $decimals = false, $dec_point = '.', $thousands_sep = ',' ) {
+
+	// If empty, set $number to (int) 0
+	if ( ! is_numeric( $number ) ) {
+		$number = 0;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_number_format', number_format( $number, $decimals, $dec_point, $thousands_sep ), $number, $decimals, $dec_point, $thousands_sep );
+}
+
+/**
+ * A bbPress specific method of formatting numeric values
+ *
+ * @since 2.1.0 bbPress (r3857)
+ *
+ * @param string $number Number to format
+ * @param string $decimals Optional. Display decimals
+ *
+ * @return string Formatted string
+ */
+function bbp_number_format_i18n( $number = 0, $decimals = false ) {
+
+	// If empty, set $number to (int) 0
+	if ( ! is_numeric( $number ) ) {
+		$number = 0;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_number_format_i18n', number_format_i18n( $number, $decimals ), $number, $decimals );
+}
+
+/** Dates *********************************************************************/
+
+/**
+ * Convert time supplied from database query into specified date format.
+ *
+ * @since 2.0.0 bbPress (r2544)
+ *
+ * @param string $time Time to convert
+ * @param string $d Optional. Default is 'U'. Either 'G', 'U', or php date
+ *                             format
+ * @param bool $translate Optional. Default is false. Whether to translate the
+ *
+ * @return string Returns timestamp
+ */
+function bbp_convert_date( $time, $d = 'U', $translate = false ) {
+	$new_time = mysql2date( $d, $time, $translate );
+
+	// Filter & return
+	return apply_filters( 'bbp_convert_date', $new_time, $d, $translate, $time );
+}
+
+/**
+ * Output formatted time to display human readable time difference.
+ *
+ * @since 2.0.0 bbPress (r2544)
+ *
+ * @param string $older_date Unix timestamp from which the difference begins.
+ * @param string $newer_date Optional. Unix timestamp from which the
+ *                            difference ends. False for current time.
+ * @param int $gmt Optional. Whether to use GMT timezone. Default is false.
+ */
+function bbp_time_since( $older_date, $newer_date = false, $gmt = false ) {
+	echo bbp_get_time_since( $older_date, $newer_date, $gmt );
+}
+	/**
+	 * Return formatted time to display human readable time difference.
+	 *
+	 * @since 2.0.0 bbPress (r2544)
+	 *
+	 * @param string $older_date Unix timestamp from which the difference begins.
+	 * @param string $newer_date Optional. Unix timestamp from which the
+	 *                            difference ends. False for current time.
+	 * @param int $gmt Optional. Whether to use GMT timezone. Default is false.
+	 *
+	 * @return string Formatted time
+	 */
+	function bbp_get_time_since( $older_date, $newer_date = false, $gmt = false ) {
+
+		// Setup the strings
+		$unknown_text   = apply_filters( 'bbp_core_time_since_unknown_text',   esc_html__( 'sometime',  'bbpress' ) );
+		$right_now_text = apply_filters( 'bbp_core_time_since_right_now_text', esc_html__( 'right now', 'bbpress' ) );
+		$ago_text       = apply_filters( 'bbp_core_time_since_ago_text',       esc_html__( '%s ago',    'bbpress' ) );
+
+		// array of time period chunks
+		$chunks = array(
+			array( YEAR_IN_SECONDS,   _n_noop( '%s year',   '%s years',   'bbpress' ) ),
+			array( MONTH_IN_SECONDS,  _n_noop( '%s month',  '%s months',  'bbpress' ) ),
+			array( WEEK_IN_SECONDS,   _n_noop( '%s week',   '%s weeks',   'bbpress' ) ),
+			array( DAY_IN_SECONDS,    _n_noop( '%s day',    '%s days',    'bbpress' ) ),
+			array( HOUR_IN_SECONDS,   _n_noop( '%s hour',   '%s hours',   'bbpress' ) ),
+			array( MINUTE_IN_SECONDS, _n_noop( '%s minute', '%s minutes', 'bbpress' ) ),
+			array( 1,                 _n_noop( '%s second', '%s seconds', 'bbpress' ) ),
+		);
+
+		// Attempt to parse non-numeric older date
+		if ( ! empty( $older_date ) && ! is_numeric( $older_date ) ) {
+			$time_chunks = explode( ':', str_replace( ' ', ':', $older_date ) );
+			$date_chunks = explode( '-', str_replace( ' ', '-', $older_date ) );
+			$older_date  = gmmktime( (int) $time_chunks[1], (int) $time_chunks[2], (int) $time_chunks[3], (int) $date_chunks[1], (int) $date_chunks[2], (int) $date_chunks[0] );
+		}
+
+		// Attempt to parse non-numeric newer date
+		if ( ! empty( $newer_date ) && ! is_numeric( $newer_date ) ) {
+			$time_chunks = explode( ':', str_replace( ' ', ':', $newer_date ) );
+			$date_chunks = explode( '-', str_replace( ' ', '-', $newer_date ) );
+			$newer_date  = gmmktime( (int) $time_chunks[1], (int) $time_chunks[2], (int) $time_chunks[3], (int) $date_chunks[1], (int) $date_chunks[2], (int) $date_chunks[0] );
+		}
+
+		// Set newer date to current time
+		if ( empty( $newer_date ) ) {
+			$newer_date = strtotime( current_time( 'mysql', $gmt ) );
+		}
+
+		// Cast both dates to ints to avoid notices & errors with invalid values
+		$newer_date = intval( $newer_date );
+		$older_date = intval( $older_date );
+
+		// Difference in seconds
+		$since = intval( $newer_date - $older_date );
+
+		// Something went wrong with date calculation and we ended up with a negative date.
+		if ( 0 > $since ) {
+			$output = $unknown_text;
+
+		// We only want to output two chunks of time here, eg:
+		//     x years, xx months
+		//     x days, xx hours
+		// so there's only two bits of calculation below:
+		} else {
+
+			// Default count values
+			$count  = 0;
+			$count2 = 0;
+
+			// Step one: the first chunk
+			for ( $i = 0, $j = count( $chunks ); $i < $j; ++$i ) {
+				$seconds = $chunks[ $i ][0];
+
+				// Finding the biggest chunk (if the chunk fits, break)
+				$count = floor( $since / $seconds );
+				if ( 0 != $count ) {
+					break;
+				}
+			}
+
+			// If $i iterates all the way to $j, then the event happened 0 seconds ago
+			if ( ! isset( $chunks[ $i ] ) ) {
+				$output = $right_now_text;
+
+			} else {
+
+				// Set output var
+				$output = sprintf( translate_nooped_plural( $chunks[ $i ][1], $count, 'bbpress' ), bbp_number_format_i18n( $count ) );
+
+				// Step two: the second chunk
+				if ( $i + 2 < $j ) {
+					$seconds2 = $chunks[ $i + 1 ][0];
+					$count2   = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
+
+					// Add to output var
+					if ( 0 != $count2 ) {
+						$output .= _x( ',', 'Separator in time since', 'bbpress' ) . ' ';
+						$output .= sprintf( translate_nooped_plural( $chunks[ $i + 1 ][1], $count2, 'bbpress' ), bbp_number_format_i18n( $count2 ) );
+					}
+				}
+
+				// Empty counts, so fallback to right now
+				if ( empty( $count ) && empty( $count2 ) ) {
+					$output = $right_now_text;
+				}
+			}
+		}
+
+		// Append 'ago' to the end of time-since if not 'right now'
+		if ( $output != $right_now_text ) {
+			$output = sprintf( $ago_text, $output );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_time_since', $output, $older_date, $newer_date );
+	}
+
+/** Revisions *****************************************************************/
+
+/**
+ * Formats the reason for editing the topic/reply.
+ *
+ * Does these things:
+ *  - Trimming
+ *  - Removing periods from the end of the string
+ *  - Trimming again
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param string $reason Optional. User submitted reason for editing.
+ * @return string Status of topic
+ */
+function bbp_format_revision_reason( $reason = '' ) {
+	$reason = (string) $reason;
+
+	// Bail if reason is empty
+	if ( empty( $reason ) ) {
+		return $reason;
+	}
+
+	// Trimming
+	$reason = trim( $reason );
+
+	// We add our own full stop.
+	while ( substr( $reason, -1 ) === '.' ) {
+		$reason = substr( $reason, 0, -1 );
+	}
+
+	// Trim again
+	$reason = trim( $reason );
+
+	return $reason;
+}
Index: /tags/2.6.6/src/includes/common/functions.php
===================================================================
--- /tags/2.6.6/src/includes/common/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/functions.php	(revision 7162)
@@ -0,0 +1,2635 @@
+<?php
+
+/**
+ * bbPress Common Functions
+ *
+ * Common functions are ones that are used by more than one component, like
+ * forums, topics, replies, users, topic tags, etc...
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return array of bbPress registered post types
+ *
+ * @since 2.6.0 bbPress (r6813)
+ *
+ * @param array $args Array of arguments to pass into `get_post_types()`
+ *
+ * @return array
+ */
+function bbp_get_post_types( $args = array() ) {
+
+	// Parse args
+	$r = bbp_parse_args( $args, array(
+		'source' => 'bbpress'
+	), 'get_post_types' );
+
+	// Return post types
+	return get_post_types( $r );
+}
+
+/** URLs **********************************************************************/
+
+/**
+ * Return the unescaped redirect_to request value
+ *
+ * @bbPress (r4655)
+ *
+ * @return string The URL to redirect to, if set
+ */
+function bbp_get_redirect_to() {
+
+	// Check 'redirect_to' request parameter
+	$retval = ! empty( $_REQUEST['redirect_to'] )
+		? $_REQUEST['redirect_to']
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_redirect_to', $retval );
+}
+
+/**
+ * Append 'view=all' to query string if it's already there from referer
+ *
+ * @since 2.0.0 bbPress (r3325)
+ *
+ * @param string $original_link Original Link to be modified
+ * @param bool $force Override bbp_get_view_all() check
+ * @return string The link with 'view=all' appended if necessary
+ */
+function bbp_add_view_all( $original_link = '', $force = false ) {
+
+	// Are we appending the view=all vars?
+	$link = ( bbp_get_view_all() || ! empty( $force ) )
+		? add_query_arg( array( 'view' => 'all' ), $original_link )
+		: $original_link;
+
+	// Filter & return
+	return apply_filters( 'bbp_add_view_all', $link, $original_link );
+}
+
+/**
+ * Remove 'view=all' from query string
+ *
+ * @since 2.0.0 bbPress (r3325)
+ *
+ * @param string $original_link Original Link to be modified
+ * @return string The link with 'view=all' appended if necessary
+ */
+function bbp_remove_view_all( $original_link = '' ) {
+
+	// Remove `view' argument
+	$link = remove_query_arg( 'view', $original_link );
+
+	// Filter & return
+	return apply_filters( 'bbp_remove_view_all', $link, $original_link );
+}
+
+/**
+ * If current user can and is viewing all topics/replies
+ *
+ * @since 2.0.0 bbPress (r3325)
+ *
+ * @param string $cap Capability used to ensure user can view all
+ *
+ * @return bool Whether current user can and is viewing all
+ */
+function bbp_get_view_all( $cap = 'moderate' ) {
+	$retval = ( ( ! empty( $_GET['view'] ) && ( 'all' === $_GET['view'] ) && current_user_can( $cap ) ) );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_get_view_all', (bool) $retval, $cap );
+}
+
+/**
+ * Assist pagination by returning correct page number
+ *
+ * @since 2.0.0 bbPress (r2628)
+ *
+ * @return int Current page number
+ */
+function bbp_get_paged() {
+	$wp_query = bbp_get_wp_query();
+
+	// Check the query var
+	if ( get_query_var( 'paged' ) ) {
+		$paged = get_query_var( 'paged' );
+
+	// Check query paged
+	} elseif ( ! empty( $wp_query->query['paged'] ) ) {
+		$paged = $wp_query->query['paged'];
+	}
+
+	// Paged found
+	if ( ! empty( $paged ) ) {
+		return (int) $paged;
+	}
+
+	// Default to first page
+	return 1;
+}
+
+/** Misc **********************************************************************/
+
+/**
+ * Return the unique non-empty values of an array.
+ *
+ * @since 2.6.0 bbPress (r6481)
+ *
+ * @param array $array Array to get values of
+ *
+ * @return array
+ */
+function bbp_get_unique_array_values( $array = array() ) {
+	return array_unique( array_filter( array_values( $array ) ) );
+}
+
+/**
+ * Fix post author id on post save
+ *
+ * When a logged in user changes the status of an anonymous reply or topic, or
+ * edits it, the post_author field is set to the logged in user's id. This
+ * function fixes that.
+ *
+ * @since 2.0.0 bbPress (r2734)
+ *
+ * @param array $data Post data
+ * @param array $postarr Original post array (includes post id)
+ * @return array Data
+ */
+function bbp_fix_post_author( $data = array(), $postarr = array() ) {
+
+	// Post is not being updated or the post_author is already 0, return
+	if ( empty( $postarr['ID'] ) || empty( $data['post_author'] ) ) {
+		return $data;
+	}
+
+	// Post is not a topic or reply, return
+	if ( ! in_array( $data['post_type'], array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ), true ) ) {
+		return $data;
+	}
+
+	// Is the post by an anonymous user?
+	if ( ( bbp_get_topic_post_type() === $data['post_type'] && ! bbp_is_topic_anonymous( $postarr['ID'] ) ) ||
+	     ( bbp_get_reply_post_type() === $data['post_type'] && ! bbp_is_reply_anonymous( $postarr['ID'] ) ) ) {
+		return $data;
+	}
+
+	// The post is being updated. It is a topic or a reply and is written by an anonymous user.
+	// Set the post_author back to 0
+	$data['post_author'] = 0;
+
+	return $data;
+}
+
+/**
+ * Check a date against the length of time something can be edited.
+ *
+ * It is recommended to leave $utc set to true and to work with UTC/GMT dates.
+ * Turning this off will use the WordPress offset which is likely undesirable.
+ *
+ * @since 2.0.0 bbPress (r3133)
+ * @since 2.6.0 bbPress (r6868) Inverted some logic and added unit tests
+ *
+ * @param string  $datetime Gets run through strtotime()
+ * @param boolean $utc      Default true. Is the timestamp in UTC?
+ *
+ * @return bool True by default, if date is past, or editing is disabled.
+ */
+function bbp_past_edit_lock( $datetime = '', $utc = true ) {
+
+	// Default value
+	$retval = true;
+
+	// Check if date and editing is allowed
+	if ( bbp_allow_content_edit() ) {
+
+		// Get number of minutes to allow editing for
+		$minutes = bbp_get_edit_lock();
+
+		// 0 minutes means forever, so can never be past edit-lock time
+		if ( 0 === $minutes ) {
+			$retval = false;
+
+		// Checking against a specific datetime
+		} elseif ( ! empty( $datetime ) ) {
+
+			// Period of time
+			$lockable = "+{$minutes} minutes";
+			if ( true === $utc ) {
+				$lockable .= " UTC";
+			}
+
+			// Now
+			$cur_time  = current_time( 'timestamp', $utc );
+
+			// Get the duration in seconds
+			$duration  = strtotime( $lockable ) - $cur_time;
+
+			// Diff the times down to seconds
+			$lock_time = strtotime( $lockable, $cur_time );
+			$past_time = strtotime( $datetime, $cur_time );
+			$diff_time = ( $lock_time - $past_time ) - $duration;
+
+			// Check if less than lock time
+			if ( $diff_time < $duration ) {
+				$retval = false;
+			}
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_past_edit_lock', $retval, $datetime, $utc );
+}
+
+/**
+ * Get number of days something should remain trashed for before it is cleaned
+ * up by WordPress Cron. If set to 0, items will skip trash and be deleted
+ * immediately.
+ *
+ * @since 2.6.0 bbPress (r6424)
+ *
+ * @param string $context Provide context for additional filtering
+ * @return int Number of days items remain in trash
+ */
+function bbp_get_trash_days( $context = 'forum' ) {
+
+	// Sanitize the context
+	$context = sanitize_key( $context );
+
+	// Check the WordPress constant
+	$days    = defined( 'EMPTY_TRASH_DAYS' )
+		? (int) EMPTY_TRASH_DAYS
+		: 30;
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_trash_days', $days, $context );
+}
+
+/** Statistics ****************************************************************/
+
+/**
+ * Get the forum statistics
+ *
+ * @since 2.0.0 bbPress (r2769)
+ * @since 2.6.0 bbPress (r6055) Introduced the `count_pending_topics` and
+ *                               `count_pending_replies` arguments.
+ *
+ * @param array $args Optional. The function supports these arguments (all
+ *                     default to true):
+ *  - count_users: Count users?
+ *  - count_forums: Count forums?
+ *  - count_topics: Count topics? If set to false, private, spammed and trashed
+ *                   topics are also not counted.
+ *  - count_pending_topics: Count pending topics? (only counted if the current
+ *                           user has edit_others_topics cap)
+ *  - count_private_topics: Count private topics? (only counted if the current
+ *                           user has read_private_topics cap)
+ *  - count_spammed_topics: Count spammed topics? (only counted if the current
+ *                           user has edit_others_topics cap)
+ *  - count_trashed_topics: Count trashed topics? (only counted if the current
+ *                           user has view_trash cap)
+ *  - count_replies: Count replies? If set to false, private, spammed and
+ *                   trashed replies are also not counted.
+ *  - count_pending_replies: Count pending replies? (only counted if the current
+ *                           user has edit_others_replies cap)
+ *  - count_private_replies: Count private replies? (only counted if the current
+ *                           user has read_private_replies cap)
+ *  - count_spammed_replies: Count spammed replies? (only counted if the current
+ *                           user has edit_others_replies cap)
+ *  - count_trashed_replies: Count trashed replies? (only counted if the current
+ *                           user has view_trash cap)
+ *  - count_tags: Count tags? If set to false, empty tags are also not counted
+ *  - count_empty_tags: Count empty tags?
+ * @return object Walked forum tree
+ */
+function bbp_get_statistics( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+
+		// Users
+		'count_users'           => true,
+
+		// Forums
+		'count_forums'          => true,
+
+		// Topics
+		'count_topics'          => true,
+		'count_pending_topics'  => true,
+		'count_private_topics'  => true,
+		'count_spammed_topics'  => true,
+		'count_trashed_topics'  => true,
+
+		// Replies
+		'count_replies'         => true,
+		'count_pending_replies' => true,
+		'count_private_replies' => true,
+		'count_spammed_replies' => true,
+		'count_trashed_replies' => true,
+
+		// Topic tags
+		'count_tags'            => true,
+		'count_empty_tags'      => true
+
+	), 'get_statistics' );
+
+	// Defaults
+	$topic_count     = $topic_count_hidden    = 0;
+	$reply_count     = $reply_count_hidden    = 0;
+	$topic_tag_count = $empty_topic_tag_count = 0;
+	$hidden_topic_title = $hidden_reply_title = '';
+
+	// Users
+	$user_count = ! empty( $r['count_users'] )
+		? bbp_get_total_users()
+		: 0;
+
+	// Forums
+	$forum_count = ! empty( $r['count_forums'] )
+		? wp_count_posts( bbp_get_forum_post_type() )->publish
+		: 0;
+
+	// Post statuses
+	$pending = bbp_get_pending_status_id();
+	$private = bbp_get_private_status_id();
+	$spam    = bbp_get_spam_status_id();
+	$trash   = bbp_get_trash_status_id();
+	$closed  = bbp_get_closed_status_id();
+
+	// Topics
+	if ( ! empty( $r['count_topics'] ) ) {
+		$all_topics  = wp_count_posts( bbp_get_topic_post_type() );
+
+		// Published (publish + closed)
+		$topic_count = $all_topics->publish + $all_topics->{$closed};
+
+		if ( current_user_can( 'read_private_topics' ) || current_user_can( 'edit_others_topics' ) || current_user_can( 'view_trash' ) ) {
+
+			// Declare empty arrays
+			$topics = $topic_titles = array();
+
+			// Pending
+			$topics['pending'] = ( ! empty( $r['count_pending_topics'] ) && current_user_can( 'edit_others_topics' ) )
+				? (int) $all_topics->{$pending}
+				: 0;
+
+			// Private
+			$topics['private'] = ( ! empty( $r['count_private_topics'] ) && current_user_can( 'read_private_topics' ) )
+				? (int) $all_topics->{$private}
+				: 0;
+
+			// Spam
+			$topics['spammed'] = ( ! empty( $r['count_spammed_topics'] ) && current_user_can( 'edit_others_topics'  ) )
+				? (int) $all_topics->{$spam}
+				: 0;
+
+			// Trash
+			$topics['trashed'] = ( ! empty( $r['count_trashed_topics'] ) && current_user_can( 'view_trash' ) )
+				? (int) $all_topics->{$trash}
+				: 0;
+
+			// Total hidden (pending + private + spam + trash)
+			$topic_count_hidden = $topics['pending'] + $topics['private'] + $topics['spammed'] + $topics['trashed'];
+
+			// Generate the hidden topic count's title attribute
+			$topic_titles[] = ! empty( $topics['pending'] )
+				? sprintf( esc_html__( 'Pending: %s', 'bbpress' ), bbp_number_format_i18n( $topics['pending'] ) )
+				: '';
+
+			$topic_titles[] = ! empty( $topics['private'] )
+				? ''//sprintf( esc_html__( 'Private: %s', 'bbpress' ), bbp_number_format_i18n( $topics['private'] ) )
+				: '';
+
+			$topic_titles[] = ! empty( $topics['spammed'] )
+				? sprintf( esc_html__( 'Spammed: %s', 'bbpress' ), bbp_number_format_i18n( $topics['spammed'] ) )
+				: '';
+
+			$topic_titles[] = ! empty( $topics['trashed'] )
+				? sprintf( esc_html__( 'Trashed: %s', 'bbpress' ), bbp_number_format_i18n( $topics['trashed'] ) )
+				: '';
+
+			// Compile the hidden topic title
+			$hidden_topic_title = implode( ' | ', array_filter( $topic_titles ) );
+		}
+	}
+
+	// Replies
+	if ( ! empty( $r['count_replies'] ) ) {
+
+		$all_replies = wp_count_posts( bbp_get_reply_post_type() );
+
+		// Published
+		$reply_count = $all_replies->publish;
+
+		if ( current_user_can( 'read_private_replies' ) || current_user_can( 'edit_others_replies' ) || current_user_can( 'view_trash' ) ) {
+
+			// Declare empty arrays
+			$replies = $reply_titles = array();
+
+			// Pending
+			$replies['pending'] = ( ! empty( $r['count_pending_replies'] ) && current_user_can( 'edit_others_replies' ) )
+				? (int) $all_replies->{$pending}
+				: 0;
+
+			// Private
+			$replies['private'] = ( ! empty( $r['count_private_replies'] ) && current_user_can( 'read_private_replies' ) )
+				? (int) $all_replies->{$private}
+				: 0;
+
+			// Spam
+			$replies['spammed'] = ( ! empty( $r['count_spammed_replies'] ) && current_user_can( 'edit_others_replies'  ) )
+				? (int) $all_replies->{$spam}
+				: 0;
+
+			// Trash
+			$replies['trashed'] = ( ! empty( $r['count_trashed_replies'] ) && current_user_can( 'view_trash' ) )
+				? (int) $all_replies->{$trash}
+				: 0;
+
+			// Total hidden (pending + private + spam + trash)
+			$reply_count_hidden = $replies['pending'] + $replies['private'] + $replies['spammed'] + $replies['trashed'];
+
+			// Generate the hidden topic count's title attribute
+			$reply_titles[] = ! empty( $replies['pending'] )
+				? sprintf( esc_html__( 'Pending: %s', 'bbpress' ), bbp_number_format_i18n( $replies['pending'] ) )
+				: '';
+
+			$reply_titles[] = ! empty( $replies['private'] )
+				? sprintf( esc_html__( 'Private: %s', 'bbpress' ), bbp_number_format_i18n( $replies['private'] ) )
+				: '';
+
+			$reply_titles[] = ! empty( $replies['spammed'] )
+				? sprintf( esc_html__( 'Spammed: %s', 'bbpress' ), bbp_number_format_i18n( $replies['spammed'] ) )
+				: '';
+
+			$reply_titles[] = ! empty( $replies['trashed'] )
+				? sprintf( esc_html__( 'Trashed: %s', 'bbpress' ), bbp_number_format_i18n( $replies['trashed'] ) )
+				: '';
+
+			// Compile the hidden replies title
+			$hidden_reply_title = implode( ' | ', array_filter( $reply_titles ) );
+		}
+	}
+
+	// Topic Tags
+	if ( ! empty( $r['count_tags'] ) && bbp_allow_topic_tags() ) {
+
+		// Get the count
+		$topic_tag_count = wp_count_terms( bbp_get_topic_tag_tax_id(), array( 'hide_empty' => true ) );
+
+		// Empty tags
+		if ( ! empty( $r['count_empty_tags'] ) && current_user_can( 'edit_topic_tags' ) ) {
+			$empty_topic_tag_count = wp_count_terms( bbp_get_topic_tag_tax_id() ) - $topic_tag_count;
+		}
+	}
+
+	// Tally the tallies
+	$counts = array_filter( array_map( 'absint', compact(
+		'user_count',
+		'forum_count',
+		'topic_count',
+		'topic_count_hidden',
+		'reply_count',
+		'reply_count_hidden',
+		'topic_tag_count',
+		'empty_topic_tag_count'
+	) ) );
+
+	// Define return value
+	$statistics = array();
+
+	// Loop through and store the integer and i18n formatted counts.
+	foreach ( $counts as $key => $count ) {
+		$statistics[ $key ]         = bbp_number_format_i18n( $count );
+		$statistics[ "{$key}_int" ] = $count;
+	}
+
+	// Add the hidden (topic/reply) count title attribute strings because we
+	// don't need to run the math functions on these (see above)
+	$statistics['hidden_topic_title'] = $hidden_topic_title;
+	$statistics['hidden_reply_title'] = $hidden_reply_title;
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_statistics', $statistics, $r, $args );
+}
+
+/** New/edit topic/reply helpers **********************************************/
+
+/**
+ * Filter anonymous post data
+ *
+ * We use REMOTE_ADDR here directly. If you are behind a proxy, you should
+ * ensure that it is properly set, such as in wp-config.php, for your
+ * environment. See {@link https://core.trac.wordpress.org/ticket/9235}
+ *
+ * Note that bbp_pre_anonymous_filters() is responsible for sanitizing each
+ * of the filtered core anonymous values here.
+ *
+ * If there are any errors, those are directly added to {@link bbPress:errors}
+ *
+ * @since 2.0.0 bbPress (r2734)
+ *
+ * @param array $args Optional. If no args are there, then $_POST values are
+ * @return bool|array False on errors, values in an array on success
+ */
+function bbp_filter_anonymous_post_data( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'bbp_anonymous_name'    => ! empty( $_POST['bbp_anonymous_name']    ) ? $_POST['bbp_anonymous_name']    : false,
+		'bbp_anonymous_email'   => ! empty( $_POST['bbp_anonymous_email']   ) ? $_POST['bbp_anonymous_email']   : false,
+		'bbp_anonymous_website' => ! empty( $_POST['bbp_anonymous_website'] ) ? $_POST['bbp_anonymous_website'] : false,
+	), 'filter_anonymous_post_data' );
+
+	// Strip invalid characters
+	$r = bbp_sanitize_anonymous_post_author( $r );
+
+	// Filter name
+	$r['bbp_anonymous_name'] = apply_filters( 'bbp_pre_anonymous_post_author_name', $r['bbp_anonymous_name'] );
+	if ( empty( $r['bbp_anonymous_name'] ) ) {
+		bbp_add_error( 'bbp_anonymous_name',  __( '<strong>Error</strong>: Invalid author name.', 'bbpress' ) );
+	}
+
+	// Filter email address
+	$r['bbp_anonymous_email'] = apply_filters( 'bbp_pre_anonymous_post_author_email', $r['bbp_anonymous_email'] );
+	if ( empty( $r['bbp_anonymous_email'] ) ) {
+		bbp_add_error( 'bbp_anonymous_email', __( '<strong>Error</strong>: Invalid email address.', 'bbpress' ) );
+	}
+
+	// Website is optional (can be empty)
+	$r['bbp_anonymous_website'] = apply_filters( 'bbp_pre_anonymous_post_author_website', $r['bbp_anonymous_website'] );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_filter_anonymous_post_data', $r, $args );
+}
+
+/**
+ * Sanitize an array of anonymous post author data
+ *
+ * @since 2.6.0 bbPress (r6400)
+ *
+ * @param array $anonymous_data
+ * @return array
+ */
+function bbp_sanitize_anonymous_post_author( $anonymous_data = array() ) {
+
+	// Make sure anonymous data is an array
+	if ( ! is_array( $anonymous_data ) ) {
+		$anonymous_data = array();
+	}
+
+	// Map meta data to comment fields (as guides for stripping invalid text)
+	$fields = array(
+		'bbp_anonymous_name'    => 'comment_author',
+		'bbp_anonymous_email'   => 'comment_author_email',
+		'bbp_anonymous_website' => 'comment_author_url'
+	);
+
+	// Setup a new return array
+	$r = $anonymous_data;
+
+	// Get the database
+	$bbp_db = bbp_db();
+
+	// Strip invalid text from fields
+	foreach ( $fields as $bbp_field => $comment_field ) {
+		if ( ! empty( $r[ $bbp_field ] ) ) {
+			$r[ $bbp_field ] = $bbp_db->strip_invalid_text_for_column( $bbp_db->comments, $comment_field, $r[ $bbp_field ] );
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_sanitize_anonymous_post_author', $r, $anonymous_data );
+}
+
+/**
+ * Update the relevant meta-data for an anonymous post author
+ *
+ * @since 2.6.0 bbPress (r6400)
+ *
+ * @param int    $post_id
+ * @param array  $anonymous_data
+ * @param string $post_type
+ */
+function bbp_update_anonymous_post_author( $post_id = 0, $anonymous_data = array(), $post_type = '' ) {
+
+	// Maybe look for anonymous
+	if ( empty( $anonymous_data ) ) {
+		$anonymous_data = bbp_filter_anonymous_post_data();
+	}
+
+	// Sanitize parameters
+	$post_id   = (int) $post_id;
+	$post_type = sanitize_key( $post_type );
+
+	// Bail if missing required data
+	if ( empty( $post_id ) || empty( $post_type ) || empty( $anonymous_data ) ) {
+		return;
+	}
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $anonymous_data, array(
+		'bbp_anonymous_name'    => '',
+		'bbp_anonymous_email'   => '',
+		'bbp_anonymous_website' => '',
+	), "update_{$post_type}" );
+
+	// Update all anonymous metas
+	foreach ( $r as $anon_key => $anon_value ) {
+
+		// Update, or delete if empty
+		! empty( $anon_value )
+			? update_post_meta( $post_id, '_' . $anon_key, (string) $anon_value, false )
+			: delete_post_meta( $post_id, '_' . $anon_key );
+	}
+}
+
+/**
+ * Check for duplicate topics/replies
+ *
+ * Check to make sure that a user is not making a duplicate post
+ *
+ * @since 2.0.0 bbPress (r2763)
+ *
+ * @param array $post_data Contains information about the comment
+ * @return bool True if it is not a duplicate, false if it is
+ */
+function bbp_check_for_duplicate( $post_data = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $post_data, array(
+		'post_author'    => 0,
+		'post_type'      => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ),
+		'post_parent'    => 0,
+		'post_content'   => '',
+		'post_status'    => bbp_get_trash_status_id(),
+		'anonymous_data' => array()
+	), 'check_for_duplicate' );
+
+	// No duplicate checks for those who can throttle
+	if ( user_can( (int) $r['post_author'], 'throttle' ) ) {
+		return true;
+	}
+
+	// Get the DB
+	$bbp_db = bbp_db();
+
+	// Default clauses
+	$join = $where = '';
+
+	// Check for anonymous post
+	if ( empty( $r['post_author'] ) && ( ! empty( $r['anonymous_data'] ) && ! empty( $r['anonymous_data']['bbp_anonymous_email'] ) ) ) {
+
+		// Sanitize the email address for querying
+		$email = sanitize_email( $r['anonymous_data']['bbp_anonymous_email'] );
+
+		// Only proceed
+		if ( ! empty( $email ) && is_email( $email ) ) {
+
+			// Get the meta SQL
+			$clauses = get_meta_sql( array( array(
+				'key'   => '_bbp_anonymous_email',
+				'value' => $email,
+			) ), 'post', $bbp_db->posts, 'ID' );
+
+			// Set clauses
+			$join  = $clauses['join'];
+
+			// "'", "%", "$" and are valid characters in email addresses
+			$where = $bbp_db->remove_placeholder_escape( $clauses['where'] );
+		}
+	}
+
+	// Unslash $r to pass through DB->prepare()
+	//
+	// @see: https://bbpress.trac.wordpress.org/ticket/2185/
+	// @see: https://core.trac.wordpress.org/changeset/23973/
+	$r = wp_unslash( $r );
+
+	// Prepare duplicate check query
+	$query  = "SELECT ID FROM {$bbp_db->posts} {$join}";
+	$query .= $bbp_db->prepare( "WHERE post_type = %s AND post_status != %s AND post_author = %d AND post_content = %s", $r['post_type'], $r['post_status'], $r['post_author'], $r['post_content'] );
+	$query .= ! empty( $r['post_parent'] )
+		? $bbp_db->prepare( " AND post_parent = %d", $r['post_parent'] )
+		: '';
+	$query .= $where;
+	$query .= " LIMIT 1";
+	$dupe   = apply_filters( 'bbp_check_for_duplicate_query', $query, $r );
+
+	// Dupe found
+	if ( $bbp_db->get_var( $dupe ) ) {
+		do_action( 'bbp_check_for_duplicate_trigger', $post_data );
+		return false;
+	}
+
+	// Dupe not found
+	return true;
+}
+
+/**
+ * Check for flooding
+ *
+ * Check to make sure that a user is not making too many posts in a short amount
+ * of time.
+ *
+ * @since 2.0.0 bbPress (r2734)
+ *
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $author_id Optional. Supply if it's a post by a logged in user.
+ *                        Do not supply if supplying $anonymous_data.
+ * @return bool True if there is no flooding, false if there is
+ */
+function bbp_check_for_flood( $anonymous_data = array(), $author_id = 0 ) {
+
+	// Allow for flood check to be skipped
+	if ( apply_filters( 'bbp_bypass_check_for_flood', false, $anonymous_data, $author_id ) ) {
+		return true;
+	}
+
+	// Option disabled. No flood checks.
+	$throttle_time = get_option( '_bbp_throttle_time' );
+	if ( empty( $throttle_time ) || ! bbp_allow_content_throttle() ) {
+		return true;
+	}
+
+	// User is anonymous, so check a transient based on the IP
+	if ( ! empty( $anonymous_data ) ) {
+		$last_posted = get_transient( '_bbp_' . bbp_current_author_ip() . '_last_posted' );
+
+		if ( ! empty( $last_posted ) && ( time() < ( $last_posted + $throttle_time ) ) ) {
+			return false;
+		}
+
+	// User is logged in, so check their last posted time
+	} elseif ( ! empty( $author_id ) ) {
+		$author_id   = (int) $author_id;
+		$last_posted = bbp_get_user_last_posted( $author_id );
+
+		if ( ! empty( $last_posted ) && ( time() < ( $last_posted + $throttle_time ) ) && ! user_can( $author_id, 'throttle' ) ) {
+			return false;
+		}
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Checks topics and replies against the discussion moderation of blocked keys
+ *
+ * @since 2.1.0 bbPress (r3581)
+ *
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $author_id Topic or reply author ID
+ * @param string $title The title of the content
+ * @param string $content The content being posted
+ * @param mixed  $strict  False for moderation_keys. True for blacklist_keys.
+ *                        String for custom keys.
+ * @return bool True if test is passed, false if fail
+ */
+function bbp_check_for_moderation( $anonymous_data = array(), $author_id = 0, $title = '', $content = '', $strict = false ) {
+
+	// Custom moderation option key
+	if ( is_string( $strict ) ) {
+		$strict = sanitize_key( $strict );
+
+		// Use custom key
+		if ( ! empty( $strict ) ) {
+			$hook_name   = $strict;
+			$option_name = "{$strict}_keys";
+
+		// Key was invalid, so default to moderation keys
+		} else {
+			$strict = false;
+		}
+	}
+
+	// Strict mode uses WordPress "blacklist" settings
+	if ( true === $strict ) {
+		$hook_name   = 'blacklist';
+		$option_name = 'blacklist_keys';
+
+	// Non-strict uses WordPress "moderation" settings
+	} elseif ( false === $strict ) {
+		$hook_name   = 'moderation';
+		$option_name = 'moderation_keys';
+	}
+
+	// Allow for moderation check to be skipped
+	if ( apply_filters( "bbp_bypass_check_for_{$hook_name}", false, $anonymous_data, $author_id, $title, $content, $strict ) ) {
+		return true;
+	}
+
+	// Maybe perform some author-specific capability checks
+	if ( ! empty( $author_id ) ) {
+
+		// Bail if user is a keymaster
+		if ( bbp_is_user_keymaster( $author_id ) ) {
+			return true;
+
+		// Bail if user can moderate
+		// https://bbpress.trac.wordpress.org/ticket/2726
+		} elseif ( ( false === $strict ) && user_can( $author_id, 'moderate' ) ) {
+			return true;
+		}
+	}
+
+	// Define local variable(s)
+	$_post     = array();
+	$match_out = '';
+
+	/** Max Links *************************************************************/
+
+	// Only check max_links when not being strict
+	if ( false === $strict ) {
+		$max_links = get_option( 'comment_max_links' );
+		if ( ! empty( $max_links ) ) {
+
+			// How many links?
+			$num_links = preg_match_all( '/(http|ftp|https):\/\//i', $content, $match_out );
+
+			// Allow for bumping the max to include the user's URL
+			if ( ! empty( $_post['url'] ) ) {
+				$num_links = apply_filters( 'comment_max_links_url', $num_links, $_post['url'], $content );
+			}
+
+			// Das ist zu viele links!
+			if ( $num_links >= $max_links ) {
+				return false;
+			}
+		}
+	}
+
+	/** Moderation ************************************************************/
+
+	/**
+	 * Filters the bbPress moderation keys.
+	 *
+	 * @since 2.6.0 bbPress (r6050)
+	 *
+	 * @param string $moderation List of moderation keys. One per new line.
+	 */
+	$moderation = apply_filters( "bbp_{$hook_name}_keys", trim( get_option( $option_name ) ) );
+
+	// Bail if no words to look for
+	if ( empty( $moderation ) ) {
+		return true;
+	}
+
+	/** User Data *************************************************************/
+
+	// Map anonymous user data
+	if ( ! empty( $anonymous_data ) ) {
+		$_post['author'] = $anonymous_data['bbp_anonymous_name'];
+		$_post['email']  = $anonymous_data['bbp_anonymous_email'];
+		$_post['url']    = $anonymous_data['bbp_anonymous_website'];
+
+	// Map current user data
+	} elseif ( ! empty( $author_id ) ) {
+
+		// Get author data
+		$user = get_userdata( $author_id );
+
+		// If data exists, map it
+		if ( ! empty( $user ) ) {
+			$_post['author'] = $user->display_name;
+			$_post['email']  = $user->user_email;
+			$_post['url']    = $user->user_url;
+		}
+	}
+
+	// Current user IP and user agent
+	$_post['user_ip'] = bbp_current_author_ip();
+	$_post['user_ua'] = bbp_current_author_ua();
+
+	// Post title and content
+	$_post['title']   = $title;
+	$_post['content'] = $content;
+
+	// Ensure HTML tags are not being used to bypass the moderation list.
+	$_post['comment_without_html'] = wp_strip_all_tags( $content );
+
+	/** Words *****************************************************************/
+
+	// Get words separated by new lines
+	$words = explode( "\n", $moderation );
+
+	// Loop through words
+	foreach ( (array) $words as $word ) {
+
+		// Trim the whitespace from the word
+		$word = trim( $word );
+
+		// Skip empty lines
+		if ( empty( $word ) ) {
+			continue;
+		}
+
+		// Do some escaping magic so that '#' chars in the
+		// spam words don't break things:
+		$word    = preg_quote( $word, '#' );
+		$pattern = "#{$word}#i";
+
+		// Loop through post data
+		foreach ( $_post as $post_data ) {
+
+			// Check each user data for current word
+			if ( preg_match( $pattern, $post_data ) ) {
+
+				// Post does not pass
+				return false;
+			}
+		}
+	}
+
+	// Check passed successfully
+	return true;
+}
+
+/**
+ * Deprecated. Use bbp_check_for_moderation() with strict flag set.
+ *
+ * @since 2.0.0 bbPress (r3446)
+ * @since 2.6.0 bbPress (r6854)
+ * @deprecated 2.6.0 Use bbp_check_for_moderation() with strict flag set
+ */
+function bbp_check_for_blacklist( $anonymous_data = array(), $author_id = 0, $title = '', $content = '' ) {
+	return bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+}
+
+/** Subscriptions *************************************************************/
+
+/**
+ * Get the "Do Not Reply" email address to use when sending subscription emails.
+ *
+ * We make some educated guesses here based on the home URL. Filters are
+ * available to customize this address further. In the future, we may consider
+ * using `admin_email` instead, though this is not normally publicized.
+ *
+ * We use `$_SERVER['SERVER_NAME']` here to mimic similar functionality in
+ * WordPress core. Previously, we used `get_home_url()` to use already validated
+ * user input, but it was causing issues in some installations.
+ *
+ * @since 2.6.0 bbPress (r5409)
+ *
+ * @see  wp_mail
+ * @see  wp_notify_postauthor
+ * @link https://bbpress.trac.wordpress.org/ticket/2618
+ *
+ * @return string
+ */
+function bbp_get_do_not_reply_address() {
+	$sitename = strtolower( $_SERVER['SERVER_NAME'] );
+	if ( substr( $sitename, 0, 4 ) === 'www.' ) {
+		$sitename = substr( $sitename, 4 );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_do_not_reply_address', 'noreply@' . $sitename );
+}
+
+/**
+ * Sends notification emails for new replies to subscribed topics
+ *
+ * Gets new post ID and check if there are subscribed users to that topic, and
+ * if there are, send notifications
+ *
+ * Note: in bbPress 2.6, we've moved away from 1 email per subscriber to 1 email
+ * with everyone BCC'd. This may have negative repercussions for email services
+ * that limit the number of addresses in a BCC field (often to around 500.) In
+ * those cases, we recommend unhooking this function and creating your own
+ * custom email script.
+ *
+ * @since 2.6.0 bbPress (r5413)
+ *
+ * @param int $reply_id ID of the newly made reply
+ * @param int $topic_id ID of the topic of the reply
+ * @param int $forum_id ID of the forum of the reply
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $reply_author ID of the topic author ID
+ * @return bool True on success, false on failure
+ */
+function bbp_notify_topic_subscribers( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $reply_author = 0 ) {
+
+	// Bail if subscriptions are turned off
+	if ( ! bbp_is_subscriptions_active() ) {
+		return false;
+	}
+
+	// Bail if importing
+	if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
+		return false;
+	}
+
+	/** Validation ************************************************************/
+
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	/** Topic *****************************************************************/
+
+	// Bail if topic is not public (includes closed)
+	if ( ! bbp_is_topic_public( $topic_id ) ) {
+		return false;
+	}
+
+	/** Reply *****************************************************************/
+
+	// Bail if reply is not published
+	if ( ! bbp_is_reply_published( $reply_id ) ) {
+		return false;
+	}
+
+	// Poster name
+	$reply_author_name = bbp_get_reply_author_display_name( $reply_id );
+
+	/** Users *****************************************************************/
+
+	// Get topic subscribers and bail if empty
+	$user_ids = bbp_get_subscribers( $topic_id );
+
+	// Remove the reply author from the list.
+	$reply_author_key = array_search( (int) $reply_author, $user_ids, true );
+	if ( false !== $reply_author_key ) {
+		unset( $user_ids[ $reply_author_key ] );
+	}
+
+	// Dedicated filter to manipulate user ID's to send emails to
+	$user_ids = (array) apply_filters( 'bbp_topic_subscription_user_ids', $user_ids, $reply_id, $topic_id );
+
+	// Bail of the reply author was the only one subscribed.
+	if ( empty( $user_ids ) ) {
+		return false;
+	}
+
+	// Get email addresses, bail if empty
+	$email_addresses = bbp_get_email_addresses_from_user_ids( $user_ids );
+	if ( empty( $email_addresses ) ) {
+		return false;
+	}
+
+	/** Mail ******************************************************************/
+
+	// Remove filters from reply content and topic title to prevent content
+	// from being encoded with HTML entities, wrapped in paragraph tags, etc...
+	bbp_remove_all_filters( 'bbp_get_reply_content' );
+	bbp_remove_all_filters( 'bbp_get_topic_title'   );
+	bbp_remove_all_filters( 'the_title'             );
+
+	// Strip tags from text and setup mail data
+	$blog_name         = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
+	$topic_title       = wp_specialchars_decode( strip_tags( bbp_get_topic_title( $topic_id ) ), ENT_QUOTES );
+	$reply_author_name = wp_specialchars_decode( strip_tags( $reply_author_name ), ENT_QUOTES );
+	$reply_content     = wp_specialchars_decode( strip_tags( bbp_get_reply_content( $reply_id ) ), ENT_QUOTES );
+	$reply_url         = bbp_get_reply_url( $reply_id );
+
+	// For plugins to filter messages per reply/topic/user
+	$message = sprintf( esc_html__( '%1$s wrote:
+
+%2$s
+
+Post Link: %3$s
+
+-----------
+
+You are receiving this email because you subscribed to a forum topic.
+
+Login and visit the topic to unsubscribe from these emails.', 'bbpress' ),
+
+		$reply_author_name,
+		$reply_content,
+		$reply_url
+	);
+
+	$message = apply_filters( 'bbp_subscription_mail_message', $message, $reply_id, $topic_id );
+	if ( empty( $message ) ) {
+		return;
+	}
+
+	// For plugins to filter titles per reply/topic/user
+	$subject = apply_filters( 'bbp_subscription_mail_title', '[' . $blog_name . '] ' . $topic_title, $reply_id, $topic_id );
+	if ( empty( $subject ) ) {
+		return;
+	}
+
+	/** Headers ***************************************************************/
+
+	// Default bbPress X-header
+	$headers    = array( bbp_get_email_header() );
+
+	// Get the noreply@ address
+	$no_reply   = bbp_get_do_not_reply_address();
+
+	// Setup "From" email address
+	$from_email = apply_filters( 'bbp_subscription_from_email', $no_reply );
+
+	// Setup the From header
+	$headers[]  = 'From: ' . get_bloginfo( 'name' ) . ' <' . $from_email . '>';
+
+	// Loop through addresses
+	foreach ( (array) $email_addresses as $address ) {
+		$headers[] = 'Bcc: ' . $address;
+	}
+
+	/** Send it ***************************************************************/
+
+	// Custom headers
+	$headers  = apply_filters( 'bbp_subscription_mail_headers', $headers  );
+ 	$to_email = apply_filters( 'bbp_subscription_to_email',     $no_reply );
+
+	// Before
+	do_action( 'bbp_pre_notify_subscribers', $reply_id, $topic_id, $user_ids );
+
+	// Send notification email
+	wp_mail( $to_email, $subject, $message, $headers );
+
+	// After
+	do_action( 'bbp_post_notify_subscribers', $reply_id, $topic_id, $user_ids );
+
+	// Restore previously removed filters
+	bbp_restore_all_filters( 'bbp_get_topic_content' );
+	bbp_restore_all_filters( 'bbp_get_topic_title'   );
+	bbp_restore_all_filters( 'the_title'             );
+
+	return true;
+}
+
+/**
+ * Sends notification emails for new topics to subscribed forums
+ *
+ * Gets new post ID and check if there are subscribed users to that forum, and
+ * if there are, send notifications
+ *
+ * Note: in bbPress 2.6, we've moved away from 1 email per subscriber to 1 email
+ * with everyone BCC'd. This may have negative repercussions for email services
+ * that limit the number of addresses in a BCC field (often to around 500.) In
+ * those cases, we recommend unhooking this function and creating your own
+ * custom email script.
+ *
+ * @since 2.5.0 bbPress (r5156)
+ *
+ * @param int $topic_id ID of the newly made reply
+ * @param int $forum_id ID of the forum for the topic
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $topic_author ID of the topic author ID
+ * @return bool True on success, false on failure
+ */
+function bbp_notify_forum_subscribers( $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $topic_author = 0 ) {
+
+	// Bail if subscriptions are turned off
+	if ( ! bbp_is_subscriptions_active() ) {
+		return false;
+	}
+
+	// Bail if importing
+	if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
+		return false;
+	}
+
+	/** Validation ************************************************************/
+
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	/**
+	 * Necessary for backwards compatibility
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/2620
+	 */
+	$user_id  = 0;
+
+	/** Topic *****************************************************************/
+
+	// Bail if topic is not public (includes closed)
+	if ( ! bbp_is_topic_public( $topic_id ) ) {
+		return false;
+	}
+
+	// Poster name
+	$topic_author_name = bbp_get_topic_author_display_name( $topic_id );
+
+	/** Users *****************************************************************/
+
+	// Get topic subscribers and bail if empty
+	$user_ids = bbp_get_subscribers( $forum_id );
+
+	// Remove the topic author from the list.
+	$topic_author_key = array_search( (int) $topic_author, $user_ids, true );
+	if ( false !== $topic_author_key ) {
+		unset( $user_ids[ $topic_author_key ] );
+	}
+
+	// Dedicated filter to manipulate user ID's to send emails to
+	$user_ids = (array) apply_filters( 'bbp_forum_subscription_user_ids', $user_ids, $topic_id, $forum_id );
+
+	// Bail of the reply author was the only one subscribed.
+	if ( empty( $user_ids ) ) {
+		return false;
+	}
+
+	// Get email addresses, bail if empty
+	$email_addresses = bbp_get_email_addresses_from_user_ids( $user_ids );
+	if ( empty( $email_addresses ) ) {
+		return false;
+	}
+
+	/** Mail ******************************************************************/
+
+	// Remove filters from reply content and topic title to prevent content
+	// from being encoded with HTML entities, wrapped in paragraph tags, etc...
+	bbp_remove_all_filters( 'bbp_get_topic_content' );
+	bbp_remove_all_filters( 'bbp_get_topic_title'   );
+	bbp_remove_all_filters( 'the_title'             );
+
+	// Strip tags from text and setup mail data
+	$blog_name         = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
+	$topic_title       = wp_specialchars_decode( strip_tags( bbp_get_topic_title( $topic_id ) ), ENT_QUOTES );
+	$topic_author_name = wp_specialchars_decode( strip_tags( $topic_author_name ), ENT_QUOTES );
+	$topic_content     = wp_specialchars_decode( strip_tags( bbp_get_topic_content( $topic_id ) ), ENT_QUOTES );
+	$topic_url         = get_permalink( $topic_id );
+
+	// For plugins to filter messages per reply/topic/user
+	$message = sprintf( esc_html__( '%1$s wrote:
+
+%2$s
+
+Topic Link: %3$s
+
+-----------
+
+You are receiving this email because you subscribed to a forum.
+
+Login and visit the topic to unsubscribe from these emails.', 'bbpress' ),
+
+		$topic_author_name,
+		$topic_content,
+		$topic_url
+	);
+
+	$message = apply_filters( 'bbp_forum_subscription_mail_message', $message, $topic_id, $forum_id, $user_id );
+	if ( empty( $message ) ) {
+		return;
+	}
+
+	// For plugins to filter titles per reply/topic/user
+	$subject = apply_filters( 'bbp_forum_subscription_mail_title', '[' . $blog_name . '] ' . $topic_title, $topic_id, $forum_id, $user_id );
+	if ( empty( $subject ) ) {
+		return;
+	}
+
+	/** Headers ***************************************************************/
+
+	// Default bbPress X-header
+	$headers    = array( bbp_get_email_header() );
+
+	// Get the noreply@ address
+	$no_reply   = bbp_get_do_not_reply_address();
+
+	// Setup "From" email address
+	$from_email = apply_filters( 'bbp_subscription_from_email', $no_reply );
+
+	// Setup the From header
+	$headers[] = 'From: ' . get_bloginfo( 'name' ) . ' <' . $from_email . '>';
+
+	// Loop through addresses
+	foreach ( (array) $email_addresses as $address ) {
+		$headers[] = 'Bcc: ' . $address;
+	}
+
+	/** Send it ***************************************************************/
+
+	// Custom headers
+	$headers  = apply_filters( 'bbp_subscription_mail_headers', $headers  );
+	$to_email = apply_filters( 'bbp_subscription_to_email',     $no_reply );
+
+	// Before
+	do_action( 'bbp_pre_notify_forum_subscribers', $topic_id, $forum_id, $user_ids );
+
+	// Send notification email
+	wp_mail( $to_email, $subject, $message, $headers );
+
+	// After
+	do_action( 'bbp_post_notify_forum_subscribers', $topic_id, $forum_id, $user_ids );
+
+	// Restore previously removed filters
+	bbp_restore_all_filters( 'bbp_get_topic_content' );
+	bbp_restore_all_filters( 'bbp_get_topic_title'   );
+	bbp_restore_all_filters( 'the_title'             );
+
+	return true;
+}
+
+/**
+ * Sends notification emails for new replies to subscribed topics
+ *
+ * This function is deprecated. Please use: bbp_notify_topic_subscribers()
+ *
+ * @since 2.0.0 bbPress (r2668)
+ *
+ * @deprecated 2.6.0 bbPress (r5412)
+ *
+ * @param int $reply_id ID of the newly made reply
+ * @param int $topic_id ID of the topic of the reply
+ * @param int $forum_id ID of the forum of the reply
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $reply_author ID of the topic author ID
+ *
+ * @return bool True on success, false on failure
+ */
+function bbp_notify_subscribers( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $reply_author = 0 ) {
+	return bbp_notify_topic_subscribers( $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author );
+}
+
+/**
+ * Return an array of user email addresses from an array of user IDs
+ *
+ * @since 2.6.0 bbPress (r6722)
+ *
+ * @param array $user_ids
+ * @return array
+ */
+function bbp_get_email_addresses_from_user_ids( $user_ids = array() ) {
+
+	// Default return value
+	$retval = array();
+
+	// Maximum number of users to get per database query
+	$limit = apply_filters( 'bbp_get_users_chunk_limit', 100 );
+
+	// Only do the work if there are user IDs to query for
+	if ( ! empty( $user_ids ) ) {
+
+		// Get total number of sets
+		$steps = ceil( count( $user_ids ) / $limit );
+		$range = array_map( 'intval', range( 1, $steps ) );
+
+		// Loop through users
+		foreach ( $range as $loop ) {
+
+			// Initial loop has no offset
+			$offset = $limit * ( $loop - 1 );
+
+			// Calculate user IDs to include
+			$loop_ids = array_slice( $user_ids, $offset, $limit );
+
+			// Skip if something went wrong
+			if ( empty( $loop_ids ) ) {
+				continue;
+			}
+
+			// Call get_users() in a way that users are cached
+			$loop_users = get_users( array(
+				'blog_id' => 0,
+				'fields'  => 'all_with_meta',
+				'include' => $loop_ids
+			) );
+
+			// Pluck emails from users
+			$loop_emails = wp_list_pluck( $loop_users, 'user_email' );
+
+			// Clean-up memory, for big user sets
+			unset( $loop_users );
+
+			// Merge users into return value
+			if ( ! empty( $loop_emails ) ) {
+				$retval = array_merge( $retval, $loop_emails );
+			}
+		}
+
+		// No duplicates
+		$retval = bbp_get_unique_array_values( $retval );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_email_addresses_from_user_ids', $retval, $user_ids, $limit );
+}
+
+/**
+ * Automatically splits bbPress emails with many Bcc recipients into chunks.
+ *
+ * This middleware is useful because topics and forums with many subscribers
+ * run into problems with Bcc limits, and many hosting companies & third-party
+ * services limit the size of a Bcc audience to prevent spamming.
+ *
+ * The default "chunk" size is 40 users per iteration, and can be filtered if
+ * desired. A future version of bbPress will introduce a setting to more easily
+ * tune this.
+ *
+ * @since 2.6.0 bbPress (r6918)
+ *
+ * @param array $args Original arguments passed to wp_mail().
+ * @return array
+ */
+function bbp_chunk_emails( $args = array() ) {
+
+	// Get the maximum number of Bcc's per chunk
+	$max_num = apply_filters( 'bbp_get_bcc_chunk_limit', 40, $args );
+
+	// Look for "bcc: " in a case-insensitive way, and split into 2 sets
+	$match       = '/^bcc: (\w+)/i';
+	$old_headers = preg_grep( $match, $args['headers'], PREG_GREP_INVERT );
+	$bcc_headers = preg_grep( $match, $args['headers'] );
+
+	// Bail if less than $max_num recipients
+	if ( empty( $bcc_headers ) || ( count( $bcc_headers ) < $max_num ) ) {
+		return $args;
+	}
+
+	// Reindex the headers arrays
+	$old_headers = array_values( $old_headers );
+	$bcc_headers = array_values( $bcc_headers );
+
+	// Break the Bcc emails into chunks
+	foreach ( array_chunk( $bcc_headers, $max_num ) as $i => $chunk ) {
+
+		// Skip the first chunk (it will get used in the original wp_mail() call)
+		if ( 0 === $i ) {
+			$first_chunk = $chunk;
+			continue;
+		}
+
+		// Send out the chunk
+		$chunk_headers = array_merge( $old_headers, $chunk );
+
+		// Recursion alert, but should be OK!
+		wp_mail(
+			$args['to'],
+			$args['subject'],
+			$args['message'],
+			$chunk_headers,
+			$args['attachments']
+		);
+	}
+
+	// Set headers to old headers + the $first_chunk of Bcc's
+	$args['headers'] = array_merge( $old_headers, $first_chunk );
+
+	// Return the reduced args, with the first chunk of Bcc's
+	return $args;
+}
+
+/**
+ * Return the string used for the bbPress specific X-header.
+ *
+ * @since 2.6.0 bbPress (r6919)
+ *
+ * @return string
+ */
+function bbp_get_email_header() {
+	return apply_filters( 'bbp_get_email_header', 'X-bbPress: ' . bbp_get_version() );
+}
+
+/** Login *********************************************************************/
+
+/**
+ * Return a clean and reliable logout URL
+ *
+ * This function is used to filter `logout_url`. If no $redirect_to value is
+ * passed, it will default to the request uri, then the forum root.
+ *
+ * See: `wp_logout_url()`
+ *
+ * @since 2.1.0 bbPress (2815)
+ *
+ * @param string $url URL used to log out
+ * @param string $redirect_to Where to redirect to?
+ *
+ * @return string The url
+ */
+function bbp_logout_url( $url = '', $redirect_to = '' ) {
+
+	// If there is no redirect in the URL, let's add one...
+	if ( ! strstr( $url, 'redirect_to' ) ) {
+
+		// Get the forum root, to maybe use as a default
+		$forum_root = bbp_get_root_url();
+
+		// No redirect passed, so check referer and fallback to request uri
+		if ( empty( $redirect_to ) ) {
+
+			// Check for a valid referer
+			$redirect_to = wp_get_referer();
+
+			// Fallback to request uri if invalid referer
+			if ( false === $redirect_to ) {
+				$redirect_to = bbp_get_url_scheme() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+			}
+		}
+
+		// Filter the $redirect_to destination
+		$filtered  = apply_filters( 'bbp_logout_url_redirect_to', $redirect_to );
+
+		// Validate $redirect_to, default to root
+		$validated = wp_validate_redirect( $filtered, $forum_root );
+
+		// Assemble $redirect_to and add it (encoded) to full $url
+		$appended  = add_query_arg( array( 'loggedout'   => 'true'   ), $validated );
+		$encoded   = urlencode( $appended );
+		$url       = add_query_arg( array( 'redirect_to' => $encoded ), $url       );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_logout_url', $url, $redirect_to );
+}
+
+/** Queries *******************************************************************/
+
+/**
+ * Merge user defined arguments into defaults array.
+ *
+ * This function is used throughout bbPress to allow for either a string or array
+ * to be merged into another array. It is identical to wp_parse_args() except
+ * it allows for arguments to be passively or aggressively filtered using the
+ * optional $filter_key parameter.
+ *
+ * @since 2.1.0 bbPress (r3839)
+ *
+ * @param string|array $args Value to merge with $defaults
+ * @param array $defaults Array that serves as the defaults.
+ * @param string $filter_key String to key the filters from
+ * @return array Merged user defined values with defaults.
+ */
+function bbp_parse_args( $args, $defaults = array(), $filter_key = '' ) {
+
+	// Setup a temporary array from $args
+	if ( is_object( $args ) ) {
+		$r = get_object_vars( $args );
+	} elseif ( is_array( $args ) ) {
+		$r =& $args;
+	} else {
+		wp_parse_str( $args, $r );
+	}
+
+	// Passively filter the args before the parse
+	if ( ! empty( $filter_key ) ) {
+		$r = apply_filters( "bbp_before_{$filter_key}_parse_args", $r, $args, $defaults );
+	}
+
+	// Parse
+	if ( is_array( $defaults ) && ! empty( $defaults ) ) {
+		$r = array_merge( $defaults, $r );
+	}
+
+	// Aggressively filter the args after the parse
+	if ( ! empty( $filter_key ) ) {
+		$r = apply_filters( "bbp_after_{$filter_key}_parse_args", $r, $args, $defaults );
+	}
+
+	// Return the parsed results
+	return $r;
+}
+
+/**
+ * Adds ability to include or exclude specific post_parent ID's
+ *
+ * @since 2.0.0 bbPress (r2996)
+ *
+ * @deprecated 2.5.8 bbPress (r5814)
+ *
+ * @global WP $wp
+ * @param string $where
+ * @param WP_Query $object
+ * @return string
+ */
+function bbp_query_post_parent__in( $where, $object = '' ) {
+	global $wp;
+
+	// Noop if WP core supports this already
+	if ( in_array( 'post_parent__in', $wp->private_query_vars, true ) ) {
+		return $where;
+	}
+
+	// Bail if no object passed
+	if ( empty( $object ) ) {
+		return $where;
+	}
+
+	// Only 1 post_parent so return $where
+	if ( is_numeric( $object->query_vars['post_parent'] ) ) {
+		return $where;
+	}
+
+	// Get the DB
+	$bbp_db = bbp_db();
+
+	// Including specific post_parent's
+	if ( ! empty( $object->query_vars['post_parent__in'] ) ) {
+		$ids    = implode( ',', wp_parse_id_list( $object->query_vars['post_parent__in'] ) );
+		$where .= " AND {$bbp_db->posts}.post_parent IN ($ids)";
+
+	// Excluding specific post_parent's
+	} elseif ( ! empty( $object->query_vars['post_parent__not_in'] ) ) {
+		$ids    = implode( ',', wp_parse_id_list( $object->query_vars['post_parent__not_in'] ) );
+		$where .= " AND {$bbp_db->posts}.post_parent NOT IN ($ids)";
+	}
+
+	// Return possibly modified $where
+	return $where;
+}
+
+/**
+ * Query the DB and get the last public post_id that has parent_id as post_parent
+ *
+ * @since 2.0.0 bbPress (r2868)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int    $parent_id Parent id.
+ * @param string $post_type Post type. Defaults to 'post'.
+ * @return int The last active post_id
+ */
+function bbp_get_public_child_last_id( $parent_id = 0, $post_type = 'post' ) {
+
+	// Bail if nothing passed
+	if ( empty( $parent_id ) ) {
+		return false;
+	}
+
+	// Which statuses
+	switch ( $post_type ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$post_status = bbp_get_public_forum_statuses();
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$post_status = bbp_get_public_topic_statuses();
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+		default :
+			$post_status = bbp_get_public_reply_statuses();
+			break;
+	}
+
+	$query = new WP_Query( array(
+		'fields'         => 'ids',
+		'post_parent'    => $parent_id,
+		'post_status'    => $post_status,
+		'post_type'      => $post_type,
+		'posts_per_page' => 1,
+		'orderby'        => array(
+			'post_date' => 'DESC',
+			'ID'        => 'DESC'
+		),
+
+		// Performance
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+	$child_id = array_shift( $query->posts );
+	unset( $query );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_public_child_last_id', $child_id, $parent_id, $post_type );
+}
+
+/**
+ * Query the database for child counts, grouped by type & status
+ *
+ * @since 2.6.0 bbPress (r6826)
+ *
+ * @param int $parent_id
+ */
+function bbp_get_child_counts( $parent_id = 0 ) {
+
+	// Create cache key
+	$parent_id    = absint( $parent_id );
+	$key          = md5( serialize( array( 'parent_id' => $parent_id, 'post_type' => bbp_get_post_types() ) ) );
+	$last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
+	$cache_key    = "bbp_child_counts:{$key}:{$last_changed}";
+
+	// Check for cache and set if needed
+	$retval = wp_cache_get( $cache_key, 'bbpress_posts' );
+	if ( false === $retval ) {
+
+		// Setup the DB & query
+		$bbp_db = bbp_db();
+		$sql    = "SELECT
+						p.post_type AS type,
+						p.post_status AS status,
+						COUNT( * ) AS count
+					FROM {$bbp_db->posts} AS p
+						LEFT JOIN {$bbp_db->postmeta} AS pm
+							ON p.ID = pm.post_id
+							AND pm.meta_key = %s
+					WHERE pm.meta_value = %s
+					GROUP BY p.post_status, p.post_type";
+
+		// Get prepare vars
+		$post_type = get_post_type( $parent_id );
+		$meta_key  = "_bbp_{$post_type}_id";
+
+		// Prepare & get results
+		$query     = $bbp_db->prepare( $sql, $meta_key, $parent_id );
+		$results   = $bbp_db->get_results( $query, ARRAY_A );
+
+		// Setup return value
+		$retval    = wp_list_pluck( $results, 'type', 'type' );
+		$statuses  = get_post_stati();
+
+		// Loop through results
+		foreach ( $results as $row ) {
+
+			// Setup empties
+			if ( ! is_array( $retval[ $row['type'] ] ) ) {
+				$retval[ $row['type'] ] = array_fill_keys( $statuses, 0 );
+			}
+
+			// Set statuses
+			$retval[ $row['type'] ][ $row['status'] ] = bbp_number_not_negative( $row['count'] );
+		}
+
+		// Always cache the results
+		wp_cache_set( $cache_key, $retval, 'bbpress_posts' );
+	}
+
+	// Make sure results are INTs
+	return (array) apply_filters( 'bbp_get_child_counts', $retval, $parent_id );
+}
+
+/**
+ * Filter a list of child counts, from `bbp_get_child_counts()`
+ *
+ * @since 2.6.0 bbPress (r6826)
+ *
+ * @param int    $parent_id  ID of post to get child counts from
+ * @param array  $types      Optional. An array of post types to filter by
+ * @param array  $statuses   Optional. An array of post statuses to filter by
+ *
+ * @return array A list of objects or object fields.
+ */
+function bbp_filter_child_counts_list( $parent_id = 0, $types = array( 'post' ), $statuses = array() ) {
+
+	// Setup local vars
+	$retval   = array();
+	$types    = array_flip( (array) $types    );
+	$statuses = array_flip( (array) $statuses );
+	$counts   = bbp_get_child_counts( $parent_id );
+
+	// Loop through counts by type
+	foreach ( $counts as $type => $type_counts ) {
+
+		// Skip if not this type
+		if ( ! isset( $types[ $type ] ) ) {
+			continue;
+		}
+
+		// Maybe filter statuses
+		if ( ! empty( $statuses ) ) {
+			$type_counts = array_intersect_key( $type_counts, $statuses );
+		}
+
+		// Add type counts to return array
+		$retval[ $type ] = $type_counts;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_filter_child_counts_list', $retval, $parent_id, $types, $statuses );
+}
+
+/**
+ * Query the DB and get a count of public children
+ *
+ * @since 2.0.0 bbPress (r2868)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int    $parent_id Parent id.
+ * @param string $post_type Post type. Defaults to 'post'.
+ * @return int The number of children
+ */
+function bbp_get_public_child_count( $parent_id = 0, $post_type = 'post' ) {
+
+	// Bail if nothing passed
+	if ( empty( $post_type ) ) {
+		return false;
+	}
+
+	// Which statuses
+	switch ( $post_type ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$post_status = bbp_get_public_forum_statuses();
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$post_status = bbp_get_public_topic_statuses();
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+		default :
+			$post_status = bbp_get_public_reply_statuses();
+			break;
+	}
+
+	// Get counts
+	$counts      = bbp_filter_child_counts_list( $parent_id, $post_type, $post_status );
+	$child_count = isset( $counts[ $post_type ] )
+		? bbp_number_not_negative( array_sum( array_values( $counts[ $post_type ] ) ) )
+		: 0;
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_public_child_count', $child_count, $parent_id, $post_type );
+}
+/**
+ * Query the DB and get a count of public children
+ *
+ * @since 2.0.0 bbPress (r2868)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int    $parent_id Parent id.
+ * @param string $post_type Post type. Defaults to 'post'.
+ * @return int The number of children
+ */
+function bbp_get_non_public_child_count( $parent_id = 0, $post_type = 'post' ) {
+
+	// Bail if nothing passed
+	if ( empty( $parent_id ) || empty( $post_type ) ) {
+		return false;
+	}
+
+	// Which statuses
+	switch ( $post_type ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$post_status = bbp_get_non_public_forum_statuses();
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$post_status = bbp_get_non_public_topic_statuses();
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+			$post_status = bbp_get_non_public_reply_statuses();
+			break;
+
+		// Any
+		default :
+			$post_status = bbp_get_public_status_id();
+			break;
+	}
+
+	// Get counts
+	$counts      = bbp_filter_child_counts_list( $parent_id, $post_type, $post_status );
+	$child_count = isset( $counts[ $post_type ] )
+		? bbp_number_not_negative( array_sum( array_values( $counts[ $post_type ] ) ) )
+		: 0;
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_non_public_child_count', $child_count, $parent_id, $post_type );
+}
+
+/**
+ * Query the DB and get the child id's of public children
+ *
+ * @since 2.0.0 bbPress (r2868)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int    $parent_id Parent id.
+ * @param string $post_type Post type. Defaults to 'post'.
+ *
+ * @return array The array of children
+ */
+function bbp_get_public_child_ids( $parent_id = 0, $post_type = 'post' ) {
+
+	// Bail if nothing passed
+	if ( empty( $parent_id ) || empty( $post_type ) ) {
+		return array();
+	}
+
+	// Which statuses
+	switch ( $post_type ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$post_status = bbp_get_public_forum_statuses();
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$post_status = bbp_get_public_topic_statuses();
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+		default :
+			$post_status = bbp_get_public_reply_statuses();
+			break;
+	}
+
+	$query = new WP_Query( array(
+		'fields'         => 'ids',
+		'post_parent'    => $parent_id,
+		'post_status'    => $post_status,
+		'post_type'      => $post_type,
+		'posts_per_page' => -1,
+		'orderby'        => array(
+			'post_date' => 'DESC',
+			'ID'        => 'DESC'
+		),
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	$child_ids = ! empty( $query->posts )
+		? $query->posts
+		: array();
+
+	unset( $query );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_public_child_ids', $child_ids, $parent_id, $post_type );
+}
+
+/**
+ * Query the DB and get the child id's of all children
+ *
+ * @since 2.0.0 bbPress (r3325)
+ *
+ * @param int $parent_id  Parent id
+ * @param string $post_type Post type. Defaults to 'post'
+ *
+ * @return array The array of children
+ */
+function bbp_get_all_child_ids( $parent_id = 0, $post_type = 'post' ) {
+
+	// Bail if nothing passed
+	if ( empty( $parent_id ) || empty( $post_type ) ) {
+		return array();
+	}
+
+	// Make cache key
+	$not_in = array( 'draft', 'future' );
+	$key    = md5( serialize( array(
+		'parent_id'   => $parent_id,
+		'post_type'   => $post_type,
+		'post_status' => $not_in
+	) ) );
+
+	// Check last changed
+	$last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
+	$cache_key    = "bbp_child_ids:{$key}:{$last_changed}";
+
+	// Check for cache and set if needed
+	$child_ids = wp_cache_get( $cache_key, 'bbpress_posts' );
+
+	// Not already cached
+	if ( false === $child_ids ) {
+
+		// Join post statuses to specifically exclude together
+		$post_status = "'" . implode( "', '", $not_in ) . "'";
+		$bbp_db      = bbp_db();
+
+		// Note that we can't use WP_Query here thanks to post_status assumptions
+		$query       = $bbp_db->prepare( "SELECT ID FROM {$bbp_db->posts} WHERE post_parent = %d AND post_status NOT IN ( {$post_status} ) AND post_type = %s ORDER BY ID DESC", $parent_id, $post_type );
+		$child_ids   = (array) $bbp_db->get_col( $query );
+
+		// Always cache the results
+		wp_cache_set( $cache_key, $child_ids, 'bbpress_posts' );
+	}
+
+	// Make sure results are INTs
+	$child_ids = wp_parse_id_list( $child_ids );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_all_child_ids', $child_ids, $parent_id, $post_type );
+}
+
+/**
+ * Prime familial post caches.
+ *
+ * This function uses _prime_post_caches() to prepare the object cache for
+ * imminent requests to post objects that aren't naturally cached by the primary
+ * WP_Query calls themselves. Post author caches are also primed.
+ *
+ * This is triggered when a `update_post_family_cache` argument is set to true.
+ *
+ * Also see: bbp_update_post_author_caches()
+ *
+ * @since 2.6.0 bbPress (r6699)
+ *
+ * @param array $objects Array of objects, fresh from a query
+ *
+ * @return bool True if some IDs were cached
+ */
+function bbp_update_post_family_caches( $objects = array() ) {
+
+	// Bail if no posts
+	if ( empty( $objects ) ) {
+		return false;
+	}
+
+	// Default value
+	$post_ids = array();
+
+	// Filter the types of IDs to prime
+	$ids = apply_filters( 'bbp_update_post_family_caches', array(
+		'_bbp_last_active_id',
+		'_bbp_last_reply_id',
+		'_bbp_last_topic_id',
+		'_bbp_reply_to'
+	), $objects );
+
+	// Get the last active IDs
+	foreach ( $objects as $object ) {
+		$object = get_post( $object );
+
+		// Skip if post ID is empty.
+		if ( empty( $object->ID ) ) {
+			continue;
+		}
+
+		// Meta IDs
+		foreach ( $ids as $key ) {
+			$post_ids[] = get_post_meta( $object->ID, $key, true );
+		}
+
+		// This post ID is already cached, but the post author may not be
+		$post_ids[] = $object->ID;
+	}
+
+	// Unique, non-zero values
+	$post_ids = bbp_get_unique_array_values( $post_ids );
+
+	// Bail if no IDs to prime
+	if ( empty( $post_ids ) ) {
+		return false;
+	}
+
+	// Prime post caches
+	_prime_post_caches( $post_ids, true, true );
+
+	// Prime post author caches
+	bbp_update_post_author_caches( $post_ids );
+
+	// Return
+	return true;
+}
+
+/**
+ * Prime post author caches.
+ *
+ * This function uses cache_users() to prepare the object cache for
+ * imminent requests to user objects that aren't naturally cached by the primary
+ * WP_Query calls themselves.
+ *
+ * This is triggered when a `update_post_author_cache` argument is set to true.
+ *
+ * @since 2.6.0 bbPress (r6699)
+ *
+ * @param array $objects Array of objects, fresh from a query
+ *
+ * @return bool True if some IDs were cached
+ */
+function bbp_update_post_author_caches( $objects = array() ) {
+
+	// Bail if no posts
+	if ( empty( $objects ) ) {
+		return false;
+	}
+
+	// Default value
+	$user_ids = array();
+
+	// Get the user IDs (could use wp_list_pluck() if this is ever a bottleneck)
+	foreach ( $objects as $object ) {
+		$object = get_post( $object );
+
+		// Skip if post does not have an author ID.
+		if ( empty( $object->post_author ) ) {
+			continue;
+		}
+
+		// If post exists, add post author to the array.
+		$user_ids[] = (int) $object->post_author;
+	}
+
+	// Unique, non-zero values
+	$user_ids = bbp_get_unique_array_values( $user_ids );
+
+	// Bail if no IDs to prime
+	if ( empty( $user_ids ) ) {
+		return false;
+	}
+
+	// Try to prime user caches
+	cache_users( $user_ids );
+
+	// Return
+	return true;
+}
+
+/** Globals *******************************************************************/
+
+/**
+ * Get the unfiltered value of a global $post's key
+ *
+ * Used most frequently when editing a forum/topic/reply
+ *
+ * @since 2.1.0 bbPress (r3694)
+ *
+ * @param string $field Name of the key
+ * @param string $context How to sanitize - raw|edit|db|display|attribute|js
+ * @return string Field value
+ */
+function bbp_get_global_post_field( $field = 'ID', $context = 'edit' ) {
+
+	// Get the post, and maybe get a field from it
+	$post   = get_post();
+	$retval = isset( $post->{$field} )
+		? sanitize_post_field( $field, $post->{$field}, $post->ID, $context )
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_global_post_field', $retval, $post, $field, $context );
+}
+
+/** Nonces ********************************************************************/
+
+/**
+ * Makes sure the user requested an action from another page on this site.
+ *
+ * To avoid security exploits within the theme.
+ *
+ * @since 2.1.0 bbPress (r4022)
+ *
+ * @param string $action Action nonce
+ * @param string $query_arg where to look for nonce in $_REQUEST
+ */
+function bbp_verify_nonce_request( $action = '', $query_arg = '_wpnonce' ) {
+
+	/** Home URL **************************************************************/
+
+	// Parse home_url() into pieces to remove query-strings, strange characters,
+	// and other funny things that plugins might to do to it.
+	$parsed_home = parse_url( home_url( '/', ( is_ssl() ? 'https' : 'http' ) ) );
+
+	// Maybe include the port, if it's included
+	if ( isset( $parsed_home['port'] ) ) {
+		$parsed_host = $parsed_home['host'] . ':' . $parsed_home['port'];
+	} else {
+		$parsed_host = $parsed_home['host'];
+	}
+
+	// Set the home URL for use in comparisons
+	$home_url = trim( strtolower( $parsed_home['scheme'] . '://' . $parsed_host . $parsed_home['path'] ), '/' );
+
+	/** Requested URL *********************************************************/
+
+	// Maybe include the port, if it's included in home_url()
+	if ( isset( $parsed_home['port'] ) && false === strpos( $_SERVER['HTTP_HOST'], ':' ) ) {
+		$request_host = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'];
+	} else {
+		$request_host = $_SERVER['HTTP_HOST'];
+	}
+
+	// Build the currently requested URL
+	$scheme        = bbp_get_url_scheme();
+	$requested_url = strtolower( $scheme . $request_host . $_SERVER['REQUEST_URI'] );
+
+	/** Look for match ********************************************************/
+
+	/**
+	 * Filters the requested URL being nonce-verified.
+	 *
+	 * Useful for configurations like reverse proxying.
+	 *
+	 * @since 2.2.0 bbPress (r4361)
+	 *
+	 * @param string $requested_url The requested URL.
+	 */
+	$matched_url = apply_filters( 'bbp_verify_nonce_request_url', $requested_url );
+
+	// Check the nonce
+	$result = isset( $_REQUEST[ $query_arg ] )
+		? wp_verify_nonce( $_REQUEST[ $query_arg ], $action )
+		: false;
+
+	// Nonce check failed
+	if ( empty( $result ) || empty( $action ) || ( strpos( $matched_url, $home_url ) !== 0 ) ) {
+		$result = false;
+	}
+
+	/**
+	 * Fires at the end of the nonce verification check.
+	 *
+	 * @since 2.1.0 bbPress (r4023)
+	 *
+	 * @param string $action Action nonce.
+	 * @param bool   $result Boolean result of nonce verification.
+	 */
+	do_action( 'bbp_verify_nonce_request', $action, $result );
+
+	return $result;
+}
+
+/** Feeds *********************************************************************/
+
+/**
+ * This function is hooked into the WordPress 'request' action and is
+ * responsible for sniffing out the query vars and serving up RSS2 feeds if
+ * the stars align and the user has requested a feed of any bbPress type.
+ *
+ * @since 2.0.0 bbPress (r3171)
+ *
+ * @param array $query_vars
+ * @return array
+ */
+function bbp_request_feed_trap( $query_vars = array() ) {
+
+	// Looking at a feed
+	if ( isset( $query_vars['feed'] ) ) {
+
+		// Forum/Topic/Reply Feed
+		if ( isset( $query_vars['post_type'] ) ) {
+
+			// Matched post type
+			$post_type = false;
+
+			// Post types to check
+			$post_types = array(
+				bbp_get_forum_post_type(),
+				bbp_get_topic_post_type(),
+				bbp_get_reply_post_type()
+			);
+
+			// Cast query vars as array outside of foreach loop
+			$qv_array = (array) $query_vars['post_type'];
+
+			// Check if this query is for a bbPress post type
+			foreach ( $post_types as $bbp_pt ) {
+				if ( in_array( $bbp_pt, $qv_array, true ) ) {
+					$post_type = $bbp_pt;
+					break;
+				}
+			}
+
+			// Looking at a bbPress post type
+			if ( ! empty( $post_type ) ) {
+
+				// Supported select query vars
+				$select_query_vars = array(
+					'p'        => false,
+					'name'     => false,
+					$post_type => false,
+				);
+
+				// Setup matched variables to select
+				foreach ( $query_vars as $key => $value ) {
+					if ( isset( $select_query_vars[ $key ] ) ) {
+						$select_query_vars[ $key ] = $value;
+					}
+				}
+
+				// Remove any empties
+				$select_query_vars = array_filter( $select_query_vars );
+
+				// What bbPress post type are we looking for feeds on?
+				switch ( $post_type ) {
+
+					// Forum
+					case bbp_get_forum_post_type() :
+
+						// Define local variable(s)
+						$meta_query = array();
+
+						// Single forum
+						if ( ! empty( $select_query_vars ) ) {
+
+							// Load up our own query
+							query_posts( array_merge( array(
+								'post_type' => bbp_get_forum_post_type(),
+								'feed'      => true
+							), $select_query_vars ) );
+
+							// Restrict to specific forum ID
+							$meta_query = array( array(
+								'key'     => '_bbp_forum_id',
+								'value'   => bbp_get_forum_id(),
+								'type'    => 'NUMERIC',
+								'compare' => '='
+							) );
+						}
+
+						// Only forum replies
+						if ( ! empty( $_GET['type'] ) && ( bbp_get_reply_post_type() === $_GET['type'] ) ) {
+
+							// The query
+							$the_query = array(
+								'author'         => 0,
+								'feed'           => true,
+								'post_type'      => bbp_get_reply_post_type(),
+								'post_parent'    => 'any',
+								'post_status'    => bbp_get_public_reply_statuses(),
+								'posts_per_page' => bbp_get_replies_per_rss_page(),
+								'order'          => 'DESC',
+								'meta_query'     => $meta_query
+							);
+
+							// Output the feed
+							bbp_display_replies_feed_rss2( $the_query );
+
+						// Only forum topics
+						} elseif ( ! empty( $_GET['type'] ) && ( bbp_get_topic_post_type() === $_GET['type'] ) ) {
+
+							// The query
+							$the_query = array(
+								'author'         => 0,
+								'feed'           => true,
+								'post_type'      => bbp_get_topic_post_type(),
+								'post_parent'    => bbp_get_forum_id(),
+								'post_status'    => bbp_get_public_topic_statuses(),
+								'posts_per_page' => bbp_get_topics_per_rss_page(),
+								'order'          => 'DESC'
+							);
+
+							// Output the feed
+							bbp_display_topics_feed_rss2( $the_query );
+
+						// All forum topics and replies
+						} else {
+
+							// Exclude private/hidden forums if not looking at single
+							if ( empty( $select_query_vars ) ) {
+								$meta_query = array( bbp_exclude_forum_ids( 'meta_query' ) );
+							}
+
+							// The query
+							$the_query = array(
+								'author'         => 0,
+								'feed'           => true,
+								'post_type'      => array( bbp_get_reply_post_type(), bbp_get_topic_post_type() ),
+								'post_parent'    => 'any',
+								'post_status'    => bbp_get_public_topic_statuses(),
+								'posts_per_page' => bbp_get_replies_per_rss_page(),
+								'order'          => 'DESC',
+								'meta_query'     => $meta_query
+							);
+
+							// Output the feed
+							bbp_display_replies_feed_rss2( $the_query );
+						}
+
+						break;
+
+					// Topic feed - Show replies
+					case bbp_get_topic_post_type() :
+
+						// Single topic
+						if ( ! empty( $select_query_vars ) ) {
+
+							// Load up our own query
+							query_posts( array_merge( array(
+								'post_type' => bbp_get_topic_post_type(),
+								'feed'      => true
+							), $select_query_vars ) );
+
+							// Output the feed
+							bbp_display_replies_feed_rss2( array( 'feed' => true ) );
+
+						// All topics
+						} else {
+
+							// The query
+							$the_query = array(
+								'author'         => 0,
+								'feed'           => true,
+								'post_parent'    => 'any',
+								'posts_per_page' => bbp_get_topics_per_rss_page(),
+								'show_stickies'  => false
+							);
+
+							// Output the feed
+							bbp_display_topics_feed_rss2( $the_query );
+						}
+
+						break;
+
+					// Replies
+					case bbp_get_reply_post_type() :
+
+						// The query
+						$the_query = array(
+							'posts_per_page' => bbp_get_replies_per_rss_page(),
+							'meta_query'     => array( array( ) ),
+							'feed'           => true
+						);
+
+						// All replies
+						if ( empty( $select_query_vars ) ) {
+							bbp_display_replies_feed_rss2( $the_query );
+						}
+
+						break;
+				}
+			}
+
+		// Single Topic Vview
+		} elseif ( isset( $query_vars[ bbp_get_view_rewrite_id() ] ) ) {
+
+			// Get the view
+			$view = $query_vars[ bbp_get_view_rewrite_id() ];
+
+			// We have a view to display a feed
+			if ( ! empty( $view ) ) {
+
+				// Get the view query
+				$the_query = bbp_get_view_query_args( $view );
+
+				// Output the feed
+				bbp_display_topics_feed_rss2( $the_query );
+			}
+		}
+
+		// @todo User profile feeds
+	}
+
+	// No feed so continue on
+	return $query_vars;
+}
+
+/** Templates ******************************************************************/
+
+/**
+ * Used to guess if page exists at requested path
+ *
+ * @since 2.0.0 bbPress (r3304)
+ *
+ * @param string $path
+ * @return mixed False if no page, Page object if true
+ */
+function bbp_get_page_by_path( $path = '' ) {
+
+	// Default to false
+	$retval = false;
+
+	// Path is not empty
+	if ( ! empty( $path ) ) {
+
+		// Pretty permalinks are on so path might exist
+		if ( get_option( 'permalink_structure' ) ) {
+			$retval = get_page_by_path( $path );
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_page_by_path', $retval, $path );
+}
+
+/**
+ * Sets the 404 status.
+ *
+ * Used primarily with topics/replies inside hidden forums.
+ *
+ * @since 2.0.0 bbPress (r3051)
+ * @since 2.6.0 bbPress (r6583) Use status_header() & nocache_headers()
+ *
+ * @param WP_Query $query  The query being checked
+ *
+ * @return bool Always returns true
+ */
+function bbp_set_404( $query = null ) {
+
+	// Global fallback
+	if ( empty( $query ) ) {
+		$query = bbp_get_wp_query();
+	}
+
+	// Setup environment
+	$query->set_404();
+
+	// Setup request
+	status_header( 404 );
+	nocache_headers();
+}
+
+/**
+ * Sets the 200 status header.
+ *
+ * @since 2.6.0 bbPress (r6583)
+ */
+function bbp_set_200() {
+	status_header( 200 );
+}
+
+/**
+ * Maybe handle the default 404 handling for some bbPress conditions
+ *
+ * Some conditions (like private/hidden forums and edits) have their own checks
+ * on `bbp_template_redirect` and are not currently 404s.
+ *
+ * @since 2.6.0 bbPress (r6555)
+ *
+ * @param bool $override Whether to override the default handler
+ * @param WP_Query $wp_query The posts query being referenced
+ *
+ * @return bool False to leave alone, true to override
+ */
+function bbp_pre_handle_404( $override = false, $wp_query = false ) {
+
+	// Handle a bbPress 404 condition
+	if ( isset( $wp_query->bbp_is_404 ) ) {
+
+		// Either force a 404 when 200, or a 200 when 404
+		$override = ( true === $wp_query->bbp_is_404 )
+			? bbp_set_404( $wp_query )
+			: bbp_set_200();
+	}
+
+	// Return, maybe overridden
+	return $override;
+}
+
+/**
+ * Maybe pre-assign the posts that are returned from a WP_Query.
+ *
+ * This effectively short-circuits the default query for posts, which is
+ * currently only used to avoid calling the main query when it's not necessary.
+ *
+ * @since 2.6.0 bbPress (r6580)
+ *
+ * @param mixed $posts Default null. Array of posts (possibly empty)
+ * @param WP_Query $wp_query
+ *
+ * @return mixed Null if no override. Array if overridden.
+ */
+function bbp_posts_pre_query( $posts = null, $wp_query = false ) {
+
+	// Custom 404 handler is set, so set posts to empty array to avoid 2 queries
+	if ( ! empty( $wp_query->bbp_is_404 ) ) {
+		$posts = array();
+	}
+
+	// Return, maybe overridden
+	return $posts;
+}
+
+/**
+ * Get scheme for a URL based on is_ssl() results.
+ *
+ * @since 2.6.0 bbPress (r6759)
+ *
+ * @return string https:// if is_ssl(), otherwise http://
+ */
+function bbp_get_url_scheme() {
+	return is_ssl()
+		? 'https://'
+		: 'http://';
+}
+
+/** Titles ********************************************************************/
+
+/**
+ * Is a title longer that the maximum title length?
+ *
+ * Uses mb_strlen() in `8bit` mode to treat strings as raw. This matches the
+ * behavior present in Comments, PHPMailer, RandomCompat, and others.
+ *
+ * @since 2.6.0 bbPress (r6783)
+ *
+ * @param string $title
+ * @return bool
+ */
+function bbp_is_title_too_long( $title = '' ) {
+	$max    = bbp_get_title_max_length();
+	$len    = mb_strlen( $title, '8bit' );
+	$result = ( $len > $max );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_title_too_long', $result, $title, $max, $len );
+}
Index: /tags/2.6.6/src/includes/common/index.php
===================================================================
--- /tags/2.6.6/src/includes/common/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/common/locale.php
===================================================================
--- /tags/2.6.6/src/includes/common/locale.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/locale.php	(revision 7162)
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * bbPress Localization
+ *
+ * @package bbPress
+ * @subpackage Localization
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Translates role name.
+ *
+ * Since the role names are in the database and not in the source there
+ * are dummy gettext calls to get them into the POT file and this function
+ * properly translates them back.
+ *
+ * The before_last_bar() call is needed, because older installs keep the roles
+ * using the old context format: 'Role name|User role' and just skipping the
+ * content after the last bar is easier than fixing them in the DB. New installs
+ * won't suffer from that problem.
+ *
+ * @see translate_user_role()
+ *
+ * @since 2.6.0 bbPress
+ *
+ * @param string $name The role name.
+ * @return string Translated role name on success, original name on failure.
+ */
+function bbp_translate_user_role( $name ) {
+	return translate_with_gettext_context( before_last_bar( $name ), 'User role', 'bbpress' );
+}
+
+/**
+ * Dummy gettext calls to get strings in the catalog.
+ *
+ * @since 2.6.0 bbPress
+ */
+function bbp_dummy_role_names() {
+
+	/* translators: user role */
+	_x( 'Keymaster', 'User role', 'bbpress' );
+
+	/* translators: user role */
+	_x( 'Moderator', 'User role', 'bbpress' );
+
+	/* translators: user role */
+	_x( 'Participant', 'User role', 'bbpress' );
+
+	/* translators: user role */
+	_x( 'Spectator', 'User role', 'bbpress' );
+
+	/* translators: user role */
+	_x( 'Blocked', 'User role', 'bbpress' );
+}
Index: /tags/2.6.6/src/includes/common/locks.php
===================================================================
--- /tags/2.6.6/src/includes/common/locks.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/locks.php	(revision 7162)
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * bbPress Locking
+ *
+ * @package bbPress
+ * @subpackage Common
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check to see if the post is currently being edited by another user.
+ *
+ * @see wp_check_post_lock()
+ *
+ * @since 2.6.0 bbPress (r6340)
+ *
+ * @param int $post_id ID of the post to check for editing
+ * @return integer False: not locked or locked by current user. Int: user ID of user with lock.
+ */
+function bbp_check_post_lock( $post_id = 0 ) {
+
+	// Bail if no post
+	if ( !$post = get_post( $post_id ) ) {
+		return false;
+	}
+
+	// Bail if no lock
+	if ( !$lock = get_post_meta( $post->ID, '_edit_lock', true ) ) {
+		return false;
+	}
+
+	// Get lock
+	$lock = explode( ':', $lock );
+	$time = $lock[0];
+	$user = (int) isset( $lock[1] )
+		? $lock[1]
+		: get_post_meta( $post->ID, '_edit_last', true );
+
+	// Filter editing window duration
+	$time_window = apply_filters( 'bbp_check_post_lock_window', 3 * MINUTE_IN_SECONDS );
+
+	// Return user who is or last edited
+	if ( ! empty( $time ) && ( $time > ( time() - $time_window ) ) && ( $user !== bbp_get_current_user_id() ) ) {
+		return (int) $user;
+	}
+
+	return false;
+}
+
+/**
+ * Mark the post as currently being edited by the current user
+ *
+ * @since 2.6.0 bbPress (r6340)
+ *
+ * @param int $post_id ID of the post to being edited
+ * @return bool|array Returns false if the post doesn't exist of there is no current user, or
+ * 	an array of the lock time and the user ID.
+ */
+function bbp_set_post_lock( $post_id = 0 ) {
+
+	// Bail if no post
+	if ( !$post = get_post( $post_id ) ) {
+		return false;
+	}
+
+	// Bail if no user
+	if ( 0 == ( $user_id = get_current_user_id() ) ) {
+		return false;
+	}
+
+	// Get time & lock value
+	$now  = time();
+	$lock = "{$now}:{$user_id}";
+
+	// Set lock value
+	update_post_meta( $post->ID, '_edit_lock', $lock );
+
+	return array( $now, $user_id );
+}
Index: /tags/2.6.6/src/includes/common/shortcodes.php
===================================================================
--- /tags/2.6.6/src/includes/common/shortcodes.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/shortcodes.php	(revision 7162)
@@ -0,0 +1,858 @@
+<?php
+
+/**
+ * bbPress Shortcodes
+ *
+ * @package bbPress
+ * @subpackage Shortcodes
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Shortcodes' ) ) :
+/**
+ * bbPress Shortcode Class
+ *
+ * @since 2.0.0 bbPress (r3031)
+ */
+class BBP_Shortcodes {
+
+	/** Vars ******************************************************************/
+
+	/**
+	 * @var array Shortcode => function
+	 */
+	public $codes = array();
+
+	/** Functions *************************************************************/
+
+	/**
+	 * Add the register_shortcodes action to bbp_init
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->add_shortcodes();
+	}
+
+	/**
+	 * Shortcode globals
+	 *
+	 * @since 2.0.0 bbPress (r3143)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+
+		// Setup the shortcodes
+		$this->codes = apply_filters( 'bbp_shortcodes', array(
+
+			/** Forums ********************************************************/
+
+			'bbp-forum-index'      => array( $this, 'display_forum_index'   ), // Forum Index
+			'bbp-forum-form'       => array( $this, 'display_forum_form'    ), // Topic form
+			'bbp-single-forum'     => array( $this, 'display_forum'         ), // Specific forum - pass an 'id' attribute
+
+			/** Topics ********************************************************/
+
+			'bbp-topic-index'      => array( $this, 'display_topic_index'   ), // Topic index
+			'bbp-topic-form'       => array( $this, 'display_topic_form'    ), // Topic form
+			'bbp-single-topic'     => array( $this, 'display_topic'         ), // Specific topic - pass an 'id' attribute
+
+			/** Topic Tags ****************************************************/
+
+			'bbp-topic-tags'       => array( $this, 'display_topic_tags'    ), // All topic tags in a cloud
+			'bbp-single-tag'       => array( $this, 'display_topics_of_tag' ), // Topics of Tag
+
+			/** Replies *******************************************************/
+
+			'bbp-reply-form'       => array( $this, 'display_reply_form'    ), // Reply form
+			'bbp-single-reply'     => array( $this, 'display_reply'         ), // Specific reply - pass an 'id' attribute
+
+			/** Views *********************************************************/
+
+			'bbp-single-view'      => array( $this, 'display_view'          ), // Single view
+
+			/** Search ********************************************************/
+
+			'bbp-search-form'      => array( $this, 'display_search_form'   ), // Search form
+			'bbp-search'           => array( $this, 'display_search'        ), // Search
+
+			/** Account *******************************************************/
+
+			'bbp-login'            => array( $this, 'display_login'         ), // Login
+			'bbp-register'         => array( $this, 'display_register'      ), // Register
+			'bbp-lost-pass'        => array( $this, 'display_lost_pass'     ), // Lost Password
+
+			/** Others *******************************************************/
+
+			'bbp-stats'            => array( $this, 'display_stats'         ), // Stats
+		) );
+	}
+
+	/**
+	 * Register the bbPress shortcodes
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 */
+	private function add_shortcodes() {
+		foreach ( (array) $this->codes as $code => $function ) {
+			add_shortcode( $code, $function );
+		}
+	}
+
+	/**
+	 * Unset some globals in the $bbp object that hold query related info
+	 *
+	 * @since 2.0.0 bbPress (r3034)
+	 */
+	private function unset_globals() {
+		$bbp = bbpress();
+
+		// Unset global queries
+		$bbp->forum_query  = new WP_Query();
+		$bbp->topic_query  = new WP_Query();
+		$bbp->reply_query  = new WP_Query();
+		$bbp->search_query = new WP_Query();
+
+		// Unset global ID's
+		$bbp->current_view_id      = 0;
+		$bbp->current_forum_id     = 0;
+		$bbp->current_topic_id     = 0;
+		$bbp->current_reply_id     = 0;
+		$bbp->current_topic_tag_id = 0;
+
+		// Reset the post data
+		wp_reset_postdata();
+	}
+
+	/** Output Buffers ********************************************************/
+
+	/**
+	 * Start an output buffer.
+	 *
+	 * This is used to put the contents of the shortcode into a variable rather
+	 * than outputting the HTML at run-time. This allows shortcodes to appear
+	 * in the correct location in the_content() instead of when it's created.
+	 *
+	 * @since 2.0.0 bbPress (r3079)
+	 *
+	 * @param string $query_name
+	 */
+	private function start( $query_name = '' ) {
+
+		// Set query name
+		bbp_set_query_name( $query_name );
+
+		// Start output buffer
+		ob_start();
+	}
+
+	/**
+	 * Return the contents of the output buffer and flush its contents.
+	 *
+	 * @since 2.0.0 bbPress (r3079)
+	 *
+	 * @return string Contents of output buffer.
+	 */
+	private function end() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Get the query name, for filter
+		$query_name = bbp_get_query_name();
+
+		// Reset the query name
+		bbp_reset_query_name();
+
+		// Return and flush the output buffer
+		$output = ob_get_clean();
+
+		// Filter & return
+		return apply_filters( 'bbp_display_shortcode', $output, $query_name );
+	}
+
+	/** Forum shortcodes ******************************************************/
+
+	/**
+	 * Display an index of all visible root level forums in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @return string
+	 */
+	public function display_forum_index() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_forum_archive' );
+
+		bbp_get_template_part( 'content', 'archive-forum' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the contents of a specific forum ID in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_forum( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( ! empty( $content ) || ( empty( $attr['id'] ) || ! is_numeric( $attr['id'] ) ) ) {
+			return $content;
+		}
+
+		// Set passed attribute to $forum_id for clarity
+		$forum_id = bbpress()->current_forum_id = $attr['id'];
+
+		// Bail if ID passed is not a forum
+		if ( ! bbp_is_forum( $forum_id ) ) {
+			return $content;
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_single_forum' );
+
+		// Check forum caps
+		if ( bbp_user_can_view_forum( array( 'forum_id' => $forum_id ) ) ) {
+			bbp_get_template_part( 'content',  'single-forum' );
+
+		// Forum is private and user does not have caps
+		} elseif ( bbp_is_forum_private( $forum_id, false ) ) {
+			bbp_get_template_part( 'feedback', 'no-access'    );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the forum form in an output buffer and return to ensure
+	 * post/page contents are displayed first.
+	 *
+	 * @since 2.1.0 bbPress (r3566)
+	 */
+	public function display_forum_form() {
+
+		// Start output buffer
+		$this->start( 'bbp_forum_form' );
+
+		// Output templates
+		bbp_get_template_part( 'form', 'forum' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Topic shortcodes ******************************************************/
+
+	/**
+	 * Display an index of all visible root level topics in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @return string
+	 */
+	public function display_topic_index() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Filter the query
+		if ( ! bbp_is_topic_archive() ) {
+			add_filter( 'bbp_before_has_topics_parse_args', array( $this, 'display_topic_index_query' ) );
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_topic_archive' );
+
+		// Output template
+		bbp_get_template_part( 'content', 'archive-topic' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the contents of a specific topic ID in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_topic( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( ! empty( $content ) || ( empty( $attr['id'] ) || ! is_numeric( $attr['id'] ) ) ) {
+			return $content;
+		}
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Set passed attribute to $forum_id for clarity
+		$topic_id = bbpress()->current_topic_id = $attr['id'];
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Bail if ID passed is not a topic
+		if ( ! bbp_is_topic( $topic_id ) ) {
+			return $content;
+		}
+
+		// Reset the queries if not in theme compat
+		if ( ! bbp_is_theme_compat_active() ) {
+
+			$bbp = bbpress();
+
+			// Reset necessary forum_query attributes for topics loop to function
+			$bbp->forum_query->query_vars['post_type'] = bbp_get_forum_post_type();
+			$bbp->forum_query->in_the_loop             = true;
+			$bbp->forum_query->post                    = get_post( $forum_id );
+
+			// Reset necessary topic_query attributes for topics loop to function
+			$bbp->topic_query->query_vars['post_type'] = bbp_get_topic_post_type();
+			$bbp->topic_query->in_the_loop             = true;
+			$bbp->topic_query->post                    = get_post( $topic_id );
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_single_topic' );
+
+		// Check forum caps
+		if ( bbp_user_can_view_forum( array( 'forum_id' => $forum_id ) ) ) {
+			bbp_get_template_part( 'content', 'single-topic' );
+
+		// Forum is private and user does not have caps
+		} elseif ( bbp_is_forum_private( $forum_id, false ) ) {
+			bbp_get_template_part( 'feedback', 'no-access'    );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the topic form in an output buffer and return to ensure
+	 * post/page contents are displayed first.
+	 *
+	 * Supports 'forum_id' attribute to display the topic form for a particular
+	 * forum. This currently has styling issues from not being wrapped in
+	 * <div id="bbpress-forums" class="bbpress-wrapper"></div> which will need to be sorted out later.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_topic_form( $attr = array(), $content = '' ) {
+
+		// Sanity check supplied info
+		if ( ! empty( $content ) || ( ! empty( $attr['forum_id'] ) && ( ! is_numeric( $attr['forum_id'] ) || ! bbp_is_forum( $attr['forum_id'] ) ) ) ) {
+			return $content;
+		}
+
+		// Unset globals
+		$this->unset_globals();
+
+		// If forum id is set, use the 'bbp_single_forum' query name
+		if ( ! empty( $attr['forum_id'] ) ) {
+
+			// Set the global current_forum_id for future requests
+			bbpress()->current_forum_id = $forum_id = bbp_get_forum_id( $attr['forum_id'] );
+
+			// Start output buffer
+			$this->start( 'bbp_single_forum' );
+
+		// No forum id was passed
+		} else {
+
+			// Set the $forum_id variable to satisfy checks below
+			$forum_id = 0;
+
+			// Start output buffer
+			$this->start( 'bbp_topic_form' );
+		}
+
+		// If the forum id is set, check forum caps else display normal topic form
+		if ( empty( $forum_id ) || bbp_user_can_view_forum( array( 'forum_id' => $forum_id ) ) ) {
+			bbp_get_template_part( 'form', 'topic' );
+
+		// Forum is private and user does not have caps
+		} elseif ( bbp_is_forum_private( $forum_id, false ) ) {
+			bbp_get_template_part( 'feedback', 'no-access' );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Replies ***************************************************************/
+
+	/**
+	 * Display the contents of a specific reply ID in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_reply( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( ! empty( $content ) || ( empty( $attr['id'] ) || ! is_numeric( $attr['id'] ) ) ) {
+			return $content;
+		}
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Set passed attribute to $reply_id for clarity
+		$reply_id = bbpress()->current_reply_id = $attr['id'];
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Bail if ID passed is not a reply
+		if ( ! bbp_is_reply( $reply_id ) ) {
+			return $content;
+		}
+
+		// Reset the queries if not in theme compat
+		if ( ! bbp_is_theme_compat_active() ) {
+
+			$bbp = bbpress();
+
+			// Reset necessary forum_query attributes for reply loop to function
+			$bbp->forum_query->query_vars['post_type'] = bbp_get_forum_post_type();
+			$bbp->forum_query->in_the_loop             = true;
+			$bbp->forum_query->post                    = get_post( $forum_id );
+
+			// Reset necessary reply_query attributes for reply loop to function
+			$bbp->reply_query->query_vars['post_type'] = bbp_get_reply_post_type();
+			$bbp->reply_query->in_the_loop             = true;
+			$bbp->reply_query->post                    = get_post( $reply_id );
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_single_reply' );
+
+		// Check forum caps
+		if ( bbp_user_can_view_forum( array( 'forum_id' => $forum_id ) ) ) {
+			bbp_get_template_part( 'content',  'single-reply' );
+
+		// Forum is private and user does not have caps
+		} elseif ( bbp_is_forum_private( $forum_id, false ) ) {
+			bbp_get_template_part( 'feedback', 'no-access'    );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the reply form in an output buffer and return to ensure
+	 * post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 */
+	public function display_reply_form() {
+
+		// Start output buffer
+		$this->start( 'bbp_reply_form' );
+
+		// Output templates
+		bbp_get_template_part( 'form', 'reply' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Topic Tags ************************************************************/
+
+	/**
+	 * Display a tag cloud of all topic tags in an output buffer and return to
+	 * ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3110)
+	 *
+	 * @return string
+	 */
+	public function display_topic_tags() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_topic_tags' );
+
+		// Output the topic tags
+		wp_tag_cloud( array(
+			'smallest' => 9,
+			'largest'  => 38,
+			'number'   => 80,
+			'taxonomy' => bbp_get_topic_tag_tax_id()
+		) );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the contents of a specific topic tag in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3110)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_topics_of_tag( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( ! empty( $content ) || ( empty( $attr['id'] ) || ! is_numeric( $attr['id'] ) ) ) {
+			return $content;
+		}
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Filter the query
+		if ( ! bbp_is_topic_tag() ) {
+			add_filter( 'bbp_before_has_topics_parse_args', array( $this, 'display_topics_of_tag_query' ) );
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_topic_tag' );
+
+		// Set passed attribute to $ag_id for clarity
+		bbpress()->current_topic_tag_id = $tag_id = $attr['id'];
+
+		// Output template
+		bbp_get_template_part( 'content', 'archive-topic' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the contents of a specific topic tag in an output buffer
+	 * and return to ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3346)
+	 *
+	 * @return string
+	 */
+	public function display_topic_tag_form() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_topic_tag_edit' );
+
+		// Output template
+		bbp_get_template_part( 'content', 'topic-tag-edit' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Views *****************************************************************/
+
+	/**
+	 * Display the contents of a specific view in an output buffer and return to
+	 * ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.0.0 bbPress (r3031)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 * @return string
+	 */
+	public function display_view( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( empty( $attr['id'] ) ) {
+			return $content;
+		}
+
+		// Set passed attribute to $view_id for clarity
+		$view_id = $attr['id'];
+
+		// Start output buffer
+		$this->start( 'bbp_single_view' );
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Set the current view ID
+		bbpress()->current_view_id = $view_id;
+
+		// Load the view
+		bbp_view_query( $view_id );
+
+		// Output template
+		bbp_get_template_part( 'content', 'single-view' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Search ****************************************************************/
+
+	/**
+	 * Display the search form in an output buffer and return to ensure
+	 * post/page contents are displayed first.
+	 *
+	 * @since 2.3.0 bbPress (r4585)
+	 */
+	public function display_search_form() {
+
+		// Bail if search is disabled
+		if ( ! bbp_allow_search() ) {
+			return;
+		}
+
+		// Start output buffer
+		$this->start( 'bbp_search_form' );
+
+		// Output templates
+		bbp_get_template_part( 'form', 'search' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display the contents of search results in an output buffer and return to
+	 * ensure that post/page contents are displayed first.
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @param array $attr
+	 * @param string $content
+	 */
+	public function display_search( $attr, $content = '' ) {
+
+		// Sanity check required info
+		if ( ! empty( $content ) ) {
+			return $content;
+		}
+
+		// Bail if search is disabled
+		if ( ! bbp_allow_search() ) {
+			return;
+		}
+
+		// Trim search attribute if it's set
+		if ( isset( $attr['search'] ) ) {
+			$attr['search'] = trim( $attr['search'] );
+		}
+
+		// Set passed attribute to $search_terms for clarity
+		$search_terms = empty( $attr['search'] )
+			? bbp_get_search_terms()
+			: $attr['search'];
+
+		// Get the rewrite ID (one time, to avoid repeated calls)
+		$rewrite_id = bbp_get_search_rewrite_id();
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Set terms for query
+		set_query_var( $rewrite_id, $search_terms );
+
+		// Start output buffer
+		$this->start( $rewrite_id );
+
+		// Output template
+		bbp_get_template_part( 'content', 'search' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Account ***************************************************************/
+
+	/**
+	 * Display a login form
+	 *
+	 * @since 2.0.0 bbPress (r3302)
+	 *
+	 * @return string
+	 */
+	public function display_login() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_login' );
+
+		// Output templates
+		if ( ! is_user_logged_in() ) {
+			bbp_get_template_part( 'form',     'user-login' );
+		} else {
+			bbp_get_template_part( 'feedback', 'logged-in'  );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display a register form
+	 *
+	 * @since 2.0.0 bbPress (r3302)
+	 *
+	 * @return string
+	 */
+	public function display_register() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_register' );
+
+		// Output templates
+		if ( ! is_user_logged_in() ) {
+			bbp_get_template_part( 'form',     'user-register' );
+		} else {
+			bbp_get_template_part( 'feedback', 'logged-in'     );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display a lost password form
+	 *
+	 * @since 2.0.0 bbPress (r3302)
+	 *
+	 * @return string
+	 */
+	public function display_lost_pass() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start( 'bbp_lost_pass' );
+
+		// Output templates
+		if ( ! is_user_logged_in() ) {
+			bbp_get_template_part( 'form',     'user-lost-pass' );
+		} else {
+			bbp_get_template_part( 'feedback', 'logged-in'      );
+		}
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Other *****************************************************************/
+
+	/**
+	 * Display forum statistics
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 *
+	 * @return string
+	 */
+	public function display_stats() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start();
+
+		// Output statistics
+		bbp_get_template_part( 'content', 'statistics' );
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/**
+	 * Display a breadcrumb
+	 *
+	 * @since 2.0.0 bbPress (r3302)
+	 *
+	 * @return string
+	 */
+	public function display_breadcrumb() {
+
+		// Unset globals
+		$this->unset_globals();
+
+		// Start output buffer
+		$this->start();
+
+		// Output breadcrumb
+		bbp_breadcrumb();
+
+		// Return contents of output buffer
+		return $this->end();
+	}
+
+	/** Query Filters *********************************************************/
+
+	/**
+	 * Filter the query for the topic index
+	 *
+	 * @since 2.1.0 bbPress (r3637)
+	 *
+	 * @param array $args
+	 * @return array
+	 */
+	public function display_topic_index_query( $args = array() ) {
+		$args['author']        = 0;
+		$args['show_stickies'] = true;
+		$args['order']         = 'DESC';
+		return $args;
+	}
+
+	/**
+	 * Filter the query for topic tags
+	 *
+	 * @since 2.1.0 bbPress (r3637)
+	 *
+	 * @param array $args
+	 * @return array
+	 */
+	public function display_topics_of_tag_query( $args = array() ) {
+		$args['tax_query'] = array( array(
+			'taxonomy' => bbp_get_topic_tag_tax_id(),
+			'field'    => 'id',
+			'terms'    => bbpress()->current_topic_tag_id
+		) );
+
+		return $args;
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/common/template.php
===================================================================
--- /tags/2.6.6/src/includes/common/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/template.php	(revision 7162)
@@ -0,0 +1,2816 @@
+<?php
+
+/**
+ * bbPress Common Template Tags
+ *
+ * Common template tags are ones that are used by more than one component, like
+ * forums, topics, replies, users, topic tags, etc...
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** URLs **********************************************************************/
+
+/**
+ * Output the forum URL
+ *
+ * @since 2.1.0 bbPress (r3979)
+ *
+ * @param string $path Additional path with leading slash
+ */
+function bbp_forums_url( $path = '/' ) {
+	echo esc_url( bbp_get_forums_url( $path ) );
+}
+	/**
+	 * Return the forum URL
+	 *
+	 * @since 2.1.0 bbPress (r3979)
+	 *
+	 * @param string $path Additional path with leading slash
+	 */
+	function bbp_get_forums_url( $path = '/' ) {
+		return home_url( bbp_get_root_slug() . $path );
+	}
+
+/**
+ * Output the forum URL
+ *
+ * @since 2.1.0 bbPress (r3979)
+ *
+ * @param string $path Additional path with leading slash
+ */
+function bbp_topics_url( $path = '/' ) {
+	echo esc_url( bbp_get_topics_url( $path ) );
+}
+	/**
+	 * Return the forum URL
+	 *
+	 * @since 2.1.0 bbPress (r3979)
+	 *
+	 * @param string $path Additional path with leading slash
+	 * @return The URL to the topics archive
+	 */
+	function bbp_get_topics_url( $path = '/' ) {
+		return home_url( bbp_get_topic_archive_slug() . $path );
+	}
+
+/** Add-on Actions ************************************************************/
+
+/**
+ * Add our custom head action to wp_head
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_head() {
+	do_action( 'bbp_head' );
+}
+
+/**
+ * Add our custom head action to wp_head
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_footer() {
+	do_action( 'bbp_footer' );
+}
+
+/** is_ ***********************************************************************/
+
+/**
+ * Check if current site is public
+ *
+ * @since 2.0.0 bbPress (r3398)
+ *
+ * @param int $site_id
+ * @return bool True if site is public, false if private
+ */
+function bbp_is_site_public( $site_id = 0 ) {
+
+	// Get the current site ID
+	if ( empty( $site_id ) ) {
+		$site_id = get_current_blog_id();
+	}
+
+	// Get the site visibility setting
+	$public = is_multisite()
+		? get_blog_option( $site_id, 'blog_public', 1 )
+		: get_option( 'blog_public', 1 );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_site_public', $public, $site_id );
+}
+
+/**
+ * Check if current page is a bbPress forum
+ *
+ * @since 2.0.0 bbPress (r2549)
+ *
+ * @param int $post_id Possible post_id to check
+ * @return bool True if it's a forum page, false if not
+ */
+function bbp_is_forum( $post_id = 0 ) {
+
+	// Assume false
+	$retval = false;
+
+	// Supplied ID is a forum
+	if ( ! empty( $post_id ) && ( bbp_get_forum_post_type() === get_post_type( $post_id ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum', $retval, $post_id );
+}
+
+/**
+ * Check if we are viewing a forum archive.
+ *
+ * @since 2.0.0 bbPress (r3251)
+ *
+ * @return bool
+ */
+function bbp_is_forum_archive() {
+
+	// Default to false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// In forum archive
+	if ( is_post_type_archive( bbp_get_forum_post_type() ) || bbp_is_query_name( 'bbp_forum_archive' ) || ! empty( $wp_query->bbp_show_topics_on_root ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_archive', $retval );
+}
+
+/**
+ * Viewing a single forum
+ *
+ * @since 2.0.0 bbPress (r3338)
+ *
+ * @return bool
+ */
+function bbp_is_single_forum() {
+
+	// Assume false
+	$retval = false;
+
+	// Edit is not a single forum
+	if ( bbp_is_forum_edit() ) {
+		return false;
+	}
+
+	// Single and a match
+	if ( is_singular( bbp_get_forum_post_type() ) || bbp_is_query_name( 'bbp_single_forum' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_forum', $retval );
+}
+
+/**
+ * Check if current page is a forum edit page
+ *
+ * @since 2.1.0 bbPress (r3553)
+ *
+ * @return bool True if it's the forum edit page, false if not
+ */
+function bbp_is_forum_edit() {
+	global $pagenow;
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_forum_edit ) && ( $wp_query->bbp_is_forum_edit === true ) ) {
+		$retval = true;
+
+	// Editing in admin
+	} elseif ( is_admin() && ( 'post.php' === $pagenow ) && ( get_post_type() === bbp_get_forum_post_type() ) && ( ! empty( $_GET['action'] ) && ( 'edit' === $_GET['action'] ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_edit', $retval );
+}
+
+/**
+ * Check if current page is a bbPress topic
+ *
+ * @since 2.0.0 bbPress (r2549)
+ *
+ * @param int $post_id Possible post_id to check
+ * @return bool True if it's a topic page, false if not
+ */
+function bbp_is_topic( $post_id = 0 ) {
+
+	// Assume false
+	$retval = false;
+
+	// Supplied ID is a topic
+	if ( ! empty( $post_id ) && ( bbp_get_topic_post_type() === get_post_type( $post_id ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic', $retval, $post_id );
+}
+
+/**
+ * Viewing a single topic
+ *
+ * @since 2.0.0 bbPress (r3338)
+ *
+ * @return bool
+ */
+function bbp_is_single_topic() {
+
+	// Assume false
+	$retval = false;
+
+	// Edit is not a single topic
+	if ( bbp_is_topic_edit() ) {
+		return false;
+	}
+
+	// Single and a match
+	if ( is_singular( bbp_get_topic_post_type() ) || bbp_is_query_name( 'bbp_single_topic' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_topic', $retval );
+}
+
+/**
+ * Check if we are viewing a topic archive.
+ *
+ * @since 2.0.0 bbPress (r3251)
+ *
+ * @return bool
+ */
+function bbp_is_topic_archive() {
+
+	// Default to false
+	$retval = false;
+
+	// In topic archive
+	if ( is_post_type_archive( bbp_get_topic_post_type() ) || bbp_is_query_name( 'bbp_topic_archive' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_archive', $retval );
+}
+
+/**
+ * Check if current page is a topic edit page
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @return bool True if it's the topic edit page, false if not
+ */
+function bbp_is_topic_edit() {
+	global $pagenow;
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_topic_edit ) && ( $wp_query->bbp_is_topic_edit === true ) ) {
+		$retval = true;
+
+	// Editing in admin
+	} elseif ( is_admin() && ( 'post.php' === $pagenow ) && ( get_post_type() === bbp_get_topic_post_type() ) && ( ! empty( $_GET['action'] ) && ( 'edit' === $_GET['action'] ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_edit', $retval );
+}
+
+/**
+ * Check if current page is a topic merge page
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @return bool True if it's the topic merge page, false if not
+ */
+function bbp_is_topic_merge() {
+
+	// Assume false
+	$retval = false;
+
+	// Check topic edit and GET params
+	if ( bbp_is_topic_edit() && ! empty( $_GET['action'] ) && ( 'merge' === $_GET['action'] ) ) {
+		return true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_merge', $retval );
+}
+
+/**
+ * Check if current page is a topic split page
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @return bool True if it's the topic split page, false if not
+ */
+function bbp_is_topic_split() {
+
+	// Assume false
+	$retval = false;
+
+	// Check topic edit and GET params
+	if ( bbp_is_topic_edit() && ! empty( $_GET['action'] ) && ( 'split' === $_GET['action'] ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_split', $retval );
+}
+
+/**
+ * Check if the current page is a topic tag
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return bool True if it's a topic tag, false if not
+ */
+function bbp_is_topic_tag() {
+
+	// Bail if topic-tags are off
+	if ( ! bbp_allow_topic_tags() ) {
+		return false;
+	}
+
+	// Return false if editing a topic tag
+	if ( bbp_is_topic_tag_edit() ) {
+		return false;
+	}
+
+	// Assume false
+	$retval = false;
+
+	// Check tax and query vars
+	if ( is_tax( bbp_get_topic_tag_tax_id() ) || ! empty( bbpress()->topic_query->is_tax ) || get_query_var( 'bbp_topic_tag' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_tag', $retval );
+}
+
+/**
+ * Check if the current page is editing a topic tag
+ *
+ * @since 2.0.0 bbPress (r3346)
+ *
+ * @return bool True if editing a topic tag, false if not
+ */
+function bbp_is_topic_tag_edit() {
+	global $pagenow, $taxnow;
+
+	// Bail if topic-tags are off
+	if ( ! bbp_allow_topic_tags() ) {
+		return false;
+	}
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_topic_tag_edit ) && ( true === $wp_query->bbp_is_topic_tag_edit ) ) {
+		$retval = true;
+
+	// Editing in admin
+	} elseif ( is_admin() && ( 'edit-tags.php' === $pagenow ) && ( bbp_get_topic_tag_tax_id() === $taxnow ) && ( ! empty( $_GET['action'] ) && ( 'edit' === $_GET['action'] ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_tag_edit', $retval );
+}
+
+/**
+ * Check if the current post type is one that comes with bbPress
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @param mixed $the_post Optional. Post object or post ID.
+ *
+ * @return bool
+ */
+function bbp_is_custom_post_type( $the_post = false ) {
+
+	// Assume false
+	$retval = false;
+
+	// Viewing one of the bbPress post types
+	if ( in_array( get_post_type( $the_post ), array(
+		bbp_get_forum_post_type(),
+		bbp_get_topic_post_type(),
+		bbp_get_reply_post_type()
+	), true ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_custom_post_type', $retval, $the_post );
+}
+
+/**
+ * Check if current page is a bbPress reply
+ *
+ * @since 2.0.0 bbPress (r2549)
+ *
+ * @param int $post_id Possible post_id to check
+ * @return bool True if it's a reply page, false if not
+ */
+function bbp_is_reply( $post_id = 0 ) {
+
+	// Assume false
+	$retval = false;
+
+	// Supplied ID is a reply
+	if ( ! empty( $post_id ) && ( bbp_get_reply_post_type() === get_post_type( $post_id ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply', $retval, $post_id );
+}
+
+/**
+ * Check if current page is a reply edit page
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @return bool True if it's the reply edit page, false if not
+ */
+function bbp_is_reply_edit() {
+	global $pagenow;
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_reply_edit ) && ( true === $wp_query->bbp_is_reply_edit ) ) {
+		$retval = true;
+
+	// Editing in admin
+	} elseif ( is_admin() && ( 'post.php' === $pagenow ) && ( get_post_type() === bbp_get_reply_post_type() ) && ( ! empty( $_GET['action'] ) && ( 'edit' === $_GET['action'] ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_edit', $retval );
+}
+
+/**
+ * Check if current page is a reply move page
+ *
+ * @return bool True if it's the reply move page, false if not
+ */
+function bbp_is_reply_move() {
+
+	// Assume false
+	$retval = false;
+
+	// Check reply edit and GET params
+	if ( bbp_is_reply_edit() && ! empty( $_GET['action'] ) && ( 'move' === $_GET['action'] ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_move', $retval );
+}
+
+/**
+ * Viewing a single reply
+ *
+ * @since 2.0.0 bbPress (r3344)
+ *
+ * @return bool
+ */
+function bbp_is_single_reply() {
+
+	// Assume false
+	$retval = false;
+
+	// Edit is not a single reply
+	if ( bbp_is_reply_edit() ) {
+		return false;
+	}
+
+	// Single and a match
+	if ( is_singular( bbp_get_reply_post_type() ) || ( bbp_is_query_name( 'bbp_single_reply' ) ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_reply', $retval );
+}
+
+/**
+ * Check if current page is a bbPress user's favorites page (profile page)
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @return bool True if it's the favorites page, false if not
+ */
+function bbp_is_favorites() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_favs ) && ( true === $wp_query->bbp_is_single_user_favs ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_favorites', $retval );
+}
+
+/**
+ * Check if current page is a bbPress user's subscriptions page (profile page)
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @return bool True if it's the subscriptions page, false if not
+ */
+function bbp_is_subscriptions() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_subs ) && ( true === $wp_query->bbp_is_single_user_subs ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_subscriptions', $retval );
+}
+
+/**
+ * Check if current page shows the topics created by a bbPress user (profile
+ * page)
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @return bool True if it's the topics created page, false if not
+ */
+function bbp_is_topics_created() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_topics ) && ( true === $wp_query->bbp_is_single_user_topics ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topics_created', $retval );
+}
+
+/**
+ * Check if current page shows the replies created by a bbPress user (profile
+ * page)
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return bool True if it's the replies created page, false if not
+ */
+function bbp_is_replies_created() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_replies ) && ( true === $wp_query->bbp_is_single_user_replies ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_replies_created', $retval );
+}
+
+/**
+ * Check if current page is the currently logged in users author page
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @return bool True if it's the user's home, false if not
+ */
+function bbp_is_user_home() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_home ) && ( true === $wp_query->bbp_is_single_user_home ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_home', $retval );
+}
+
+/**
+ * Check if current page is the currently logged in users author edit page
+ *
+ * @since 2.1.0 bbPress (r3918)
+ *
+ * @return bool True if it's the user's home, false if not
+ */
+function bbp_is_user_home_edit() {
+
+	// Assume false
+	$retval = false;
+
+	if ( bbp_is_user_home() && bbp_is_single_user_edit() ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_home_edit', $retval );
+}
+
+/**
+ * Check if current page is a user profile page
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @return bool True if it's a user's profile page, false if not
+ */
+function bbp_is_single_user() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user ) && ( true === $wp_query->bbp_is_single_user ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user', $retval );
+}
+
+/**
+ * Check if current page is a user profile edit page
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @return bool True if it's a user's profile edit page, false if not
+ */
+function bbp_is_single_user_edit() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_edit ) && ( true === $wp_query->bbp_is_single_user_edit ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user_edit', $retval );
+}
+
+/**
+ * Check if current page is a user profile page
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return bool True if it's a user's profile page, false if not
+ */
+function bbp_is_single_user_profile() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_profile ) && ( true === $wp_query->bbp_is_single_user_profile ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user_profile', $retval );
+}
+
+/**
+ * Check if current page is a user topics created page
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return bool True if it's a user's topics page, false if not
+ */
+function bbp_is_single_user_topics() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_topics ) && ( true === $wp_query->bbp_is_single_user_topics ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user_topics', $retval );
+}
+
+/**
+ * Check if current page is a user replies created page
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return bool True if it's a user's replies page, false if not
+ */
+function bbp_is_single_user_replies() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_replies ) && ( true === $wp_query->bbp_is_single_user_replies ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user_replies', $retval );
+}
+
+/**
+ * Check if current page is a user engagements page
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @return bool True if it's a user's replies page, false if not
+ */
+function bbp_is_single_user_engagements() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_single_user_engagements ) && ( true === $wp_query->bbp_is_single_user_engagements ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_user_engagements', $retval );
+}
+
+/**
+ * Check if current page is a view page
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @global WP_Query $wp_query To check if WP_Query::bbp_is_view is true
+ * @return bool Is it a view page?
+ */
+function bbp_is_single_view() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_view ) && ( true === $wp_query->bbp_is_view ) ) {
+		$retval = true;
+	}
+
+	// Check query name
+	if ( empty( $retval ) && bbp_is_query_name( 'bbp_single_view' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_single_view', $retval );
+}
+
+/**
+ * Check if current page is a search page
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @global WP_Query $wp_query To check if WP_Query::bbp_is_search is true
+ * @return bool Is it a search page?
+ */
+function bbp_is_search() {
+
+	// Bail if search is disabled
+	if ( ! bbp_allow_search() ) {
+		return false;
+	}
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Get the rewrite ID (one time, to avoid repeated calls)
+	$rewrite_id = bbp_get_search_rewrite_id();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_search ) && ( true === $wp_query->bbp_is_search ) ) {
+		$retval = true;
+	}
+
+	// Check query name
+	if ( empty( $retval ) && bbp_is_query_name( $rewrite_id ) ) {
+		$retval = true;
+	}
+
+	// Check $_GET
+	if ( empty( $retval ) && isset( $_REQUEST[ $rewrite_id ] ) && empty( $_REQUEST[ $rewrite_id ] ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_search', $retval );
+}
+
+/**
+ * Check if current page is a search results page
+ *
+ * @since 2.4.0 bbPress (r4919)
+ *
+ * @global WP_Query $wp_query To check if WP_Query::bbp_is_search is true
+ * @return bool Is it a search page?
+ */
+function bbp_is_search_results() {
+
+	// Bail if search is disabled
+	if ( ! bbp_allow_search() ) {
+		return false;
+	}
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_search_terms ) ) {
+		$retval = true;
+	}
+
+	// Check query name
+	if ( empty( $retval ) && bbp_is_query_name( 'bbp_search_results' ) ) {
+		$retval = true;
+	}
+
+	// Check $_REQUEST
+	if ( empty( $retval ) && ! empty( $_REQUEST[ bbp_get_search_rewrite_id() ] ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_search_results', $retval );
+}
+
+/**
+ * Check if current page is an edit page
+ *
+ * @since 2.1.0 bbPress (r3585)
+ *
+ * @return bool True if it's the edit page, false if not
+ */
+function bbp_is_edit() {
+
+	// Assume false
+	$retval = false;
+
+	// Get the main query global
+	$wp_query = bbp_get_wp_query();
+
+	// Check query
+	if ( ! empty( $wp_query->bbp_is_edit ) && ( $wp_query->bbp_is_edit === true ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_edit', $retval );
+}
+
+/**
+ * Use the above is_() functions to output a body class for each scenario
+ *
+ * @since 2.0.0 bbPress (r2926)
+ *
+ * @param array $wp_classes
+ * @param array $custom_classes
+ * @return array Body Classes
+ */
+function bbp_body_class( $wp_classes, $custom_classes = false ) {
+
+	$bbp_classes = array();
+
+	/** Archives **************************************************************/
+
+	if ( bbp_is_forum_archive() ) {
+		$bbp_classes[] = bbp_get_forum_post_type() . '-archive';
+
+	} elseif ( bbp_is_topic_archive() ) {
+		$bbp_classes[] = bbp_get_topic_post_type() . '-archive';
+
+	/** Topic Tags ************************************************************/
+
+	} elseif ( bbp_is_topic_tag() ) {
+		$bbp_classes[] = bbp_get_topic_tag_tax_id();
+		$bbp_classes[] = bbp_get_topic_tag_tax_id() . '-' . bbp_get_topic_tag_slug();
+		$bbp_classes[] = bbp_get_topic_tag_tax_id() . '-' . bbp_get_topic_tag_id();
+	} elseif ( bbp_is_topic_tag_edit() ) {
+		$bbp_classes[] = bbp_get_topic_tag_tax_id() . '-edit';
+		$bbp_classes[] = bbp_get_topic_tag_tax_id() . '-' . bbp_get_topic_tag_slug() . '-edit';
+		$bbp_classes[] = bbp_get_topic_tag_tax_id() . '-' . bbp_get_topic_tag_id()   . '-edit';
+
+	/** Components ************************************************************/
+
+	} elseif ( bbp_is_single_forum() ) {
+		$bbp_classes[] = bbp_get_forum_post_type();
+
+	} elseif ( bbp_is_single_topic() ) {
+		$bbp_classes[] = bbp_get_topic_post_type();
+
+	} elseif ( bbp_is_single_reply() ) {
+		$bbp_classes[] = bbp_get_reply_post_type();
+
+	} elseif ( bbp_is_topic_edit() ) {
+		$bbp_classes[] = bbp_get_topic_post_type() . '-edit';
+
+	} elseif ( bbp_is_topic_merge() ) {
+		$bbp_classes[] = bbp_get_topic_post_type() . '-merge';
+
+	} elseif ( bbp_is_topic_split() ) {
+		$bbp_classes[] = bbp_get_topic_post_type() . '-split';
+
+	} elseif ( bbp_is_reply_edit() ) {
+		$bbp_classes[] = bbp_get_reply_post_type() . '-edit';
+
+	} elseif ( bbp_is_reply_move() ) {
+		$bbp_classes[] = bbp_get_reply_post_type() . '-move';
+
+	} elseif ( bbp_is_single_view() ) {
+		$bbp_classes[] = 'bbp-view';
+		$bbp_classes[] = 'bbp-view-' . bbp_get_view_id();
+
+	/** User ******************************************************************/
+
+	} elseif ( bbp_is_single_user_edit() ) {
+		$bbp_classes[] = 'bbp-user-edit';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_single_user() ) {
+		$bbp_classes[] = 'bbp-user-page';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_user_home() ) {
+		$bbp_classes[] = 'bbp-user-home';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_user_home_edit() ) {
+		$bbp_classes[] = 'bbp-user-home-edit';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_topics_created() ) {
+		$bbp_classes[] = 'bbp-topics-created';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_replies_created() ) {
+		$bbp_classes[] = 'bbp-replies-created';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_favorites() ) {
+		$bbp_classes[] = 'bbp-favorites';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	} elseif ( bbp_is_subscriptions() ) {
+		$bbp_classes[] = 'bbp-subscriptions';
+		$bbp_classes[] = 'single';
+		$bbp_classes[] = 'singular';
+
+	/** Search ****************************************************************/
+
+	} elseif ( bbp_is_search() ) {
+		$bbp_classes[] = 'bbp-search';
+		$bbp_classes[] = 'forum-search';
+
+	} elseif ( bbp_is_search_results() ) {
+		$bbp_classes[] = 'bbp-search-results';
+		$bbp_classes[] = 'forum-search-results';
+
+	/** Shortcodes ************************************************************/
+
+	} elseif ( bbp_has_shortcode() ) {
+		$bbp_classes[] = 'bbp-shortcode';
+	}
+
+	/** Clean up **************************************************************/
+
+	// Add bbPress class if we are within a bbPress page
+	if ( ! empty( $bbp_classes ) ) {
+		$bbp_classes[] = 'bbpress';
+	}
+
+	// Merge WP classes with bbPress classes and remove any duplicates
+	$classes = array_unique( array_merge( (array) $bbp_classes, (array) $wp_classes ) );
+
+	// Deprecated filter (do not use)
+	$classes = apply_filters( 'bbp_get_the_body_class', $classes, $bbp_classes, $wp_classes, $custom_classes );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_body_class', $classes, $bbp_classes, $wp_classes, $custom_classes );
+}
+
+/**
+ * Check if text contains a bbPress shortcode.
+ *
+ * Loops through registered bbPress shortcodes and keeps track of which ones
+ * were used in a blob of text. If no text is passed, the current global post
+ * content is assumed.
+ *
+ * A preliminary strpos() is performed before looping through each shortcode, to
+ * prevent unnecessarily processing.
+ *
+ * @since 2.6.0
+ *
+ * @param string $text
+ * @return bool
+ */
+function bbp_has_shortcode( $text = '' ) {
+
+	// Default return value
+	$retval = false;
+	$found  = array();
+
+	// Fallback to global post_content
+	if ( empty( $text ) && is_singular() ) {
+		$text = bbp_get_global_post_field( 'post_content', 'raw' );
+	}
+
+	// Skip if empty, or string doesn't contain the bbPress shortcode prefix
+	if ( ! empty( $text ) && ( false !== strpos( $text, '[bbp' ) ) ) {
+
+		// Get possible shortcodes
+		$codes = array_keys( bbpress()->shortcodes->codes );
+
+		// Loop through codes
+		foreach ( $codes as $code ) {
+
+			// Looking for shortcode in text
+			if ( has_shortcode( $text, $code ) ) {
+				$retval  = true;
+				$found[] = $code;
+			}
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_has_shortcode', $retval, $found, $text );
+}
+
+/**
+ * Use the above is_() functions to return if in any bbPress page
+ *
+ * @since 2.0.0 bbPress (r3344)
+ *
+ * @return bool In a bbPress page
+ */
+function is_bbpress() {
+
+	// Default to false
+	$retval = false;
+
+	// Bail if main query has not been populated.
+	if ( ! bbp_get_wp_query() ) {
+		_doing_it_wrong( __FUNCTION__, esc_html__( 'Conditional query tags do not work before the query is run. Before then, they always return false.', 'bbpress' ), '2.7.0' );
+		return $retval;
+	}
+
+	/** Archives **************************************************************/
+
+	if ( bbp_is_forum_archive() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topic_archive() ) {
+		$retval = true;
+
+	/** Topic Tags ************************************************************/
+
+	} elseif ( bbp_is_topic_tag() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topic_tag_edit() ) {
+		$retval = true;
+
+	/** Components ************************************************************/
+
+	} elseif ( bbp_is_single_forum() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_single_topic() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_single_reply() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topic_edit() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topic_merge() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topic_split() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_reply_edit() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_reply_move() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_single_view() ) {
+		$retval = true;
+
+	/** User ******************************************************************/
+
+	} elseif ( bbp_is_single_user_edit() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_single_user() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_user_home() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_user_home_edit() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_topics_created() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_replies_created() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_favorites() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_subscriptions() ) {
+		$retval = true;
+
+	/** Search ****************************************************************/
+
+	} elseif ( bbp_is_search() ) {
+		$retval = true;
+
+	} elseif ( bbp_is_search_results() ) {
+		$retval = true;
+
+	/** Shortcodes ************************************************************/
+
+	} elseif ( bbp_has_shortcode() ) {
+		$retval = true;
+	}
+
+	/** Done ******************************************************************/
+
+	// Filter & return
+	return (bool) apply_filters( 'is_bbpress', $retval );
+}
+
+/** Forms *********************************************************************/
+
+/**
+ * Output the login form action url
+ *
+ * @since 2.0.0 bbPress (r2815)
+ *
+ * @param array $args This function supports these arguments:
+ *  - action: The action being taken
+ *  - context: The context the action is being taken from
+ */
+function bbp_wp_login_action( $args = array() ) {
+	echo esc_url( bbp_get_wp_login_action( $args ) );
+}
+
+	/**
+	 * Return the login form action url
+	 *
+	 * @since 2.6.0 bbPress (r5684)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - action: The action being taken
+	 *  - context: The context the action is being taken from
+	 */
+	function bbp_get_wp_login_action( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'action'  => '',
+			'context' => '',
+			'url'     => 'wp-login.php'
+		), 'login_action' );
+
+		// Add action as query arg
+		$login_url = ! empty( $r['action'] )
+			? add_query_arg( array( 'action' => $r['action'] ), $r['url'] )
+			: $r['url'];
+
+		$login_url = site_url( $login_url, $r['context'] );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_wp_login_action', $login_url, $r, $args );
+	}
+
+/**
+ * Output hidden request URI field for user forms.
+ *
+ * The referer link is the current Request URI from the server super global. To
+ * check the field manually, use bbp_get_redirect_to().
+ *
+ * @since 2.0.0 bbPress (r2815)
+ *
+ * @param string $redirect_to Pass a URL to redirect to
+ */
+function bbp_redirect_to_field( $redirect_to = '' ) {
+
+	// Make sure we are directing somewhere
+	if ( empty( $redirect_to ) ) {
+		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+			$redirect_to = bbp_get_url_scheme() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+		} else {
+			$redirect_to = wp_get_referer();
+		}
+	}
+
+	// Remove loggedout query arg if it's there
+	$redirect_to    = remove_query_arg( 'loggedout', $redirect_to );
+	$redirect_field = '<input type="hidden" id="bbp_redirect_to" name="redirect_to" value="' . esc_url( $redirect_to ) . '" />';
+
+	// Filter & return
+	echo apply_filters( 'bbp_redirect_to_field', $redirect_field, $redirect_to );
+}
+
+/**
+ * Echo sanitized $_REQUEST value.
+ *
+ * Use the $input_type parameter to properly process the value. This
+ * ensures correct sanitization of the value for the receiving input.
+ *
+ * @since 2.0.0 bbPress (r2815)
+ *
+ * @param string $request Name of $_REQUEST to look for
+ * @param string $input_type Type of input. Default: text. Accepts:
+ *                            textarea|password|select|radio|checkbox
+ */
+function bbp_sanitize_val( $request = '', $input_type = 'text' ) {
+	echo bbp_get_sanitize_val( $request, $input_type );
+}
+	/**
+	 * Return sanitized $_REQUEST value.
+	 *
+	 * Use the $input_type parameter to properly process the value. This
+	 * ensures correct sanitization of the value for the receiving input.
+	 *
+	 * @since 2.0.0 bbPress (r2815)
+	 *
+	 * @param string $request Name of $_REQUEST to look for
+	 * @param string $input_type Type of input. Default: text. Accepts:
+	 *                            textarea|password|select|radio|checkbox
+	 *
+	 * @return string Sanitized value ready for screen display
+	 */
+	function bbp_get_sanitize_val( $request = '', $input_type = 'text' ) {
+
+		// Check that requested
+		if ( empty( $_REQUEST[ $request ] ) ) {
+			return false;
+		}
+
+		// Set request varaible
+		$pre_ret_val = $_REQUEST[ $request ];
+
+		// Treat different kinds of fields in different ways
+		switch ( $input_type ) {
+			case 'text'     :
+			case 'textarea' :
+				$retval = esc_attr( wp_unslash( $pre_ret_val ) );
+				break;
+
+			case 'password' :
+			case 'select'   :
+			case 'radio'    :
+			case 'checkbox' :
+			default :
+				$retval = esc_attr( $pre_ret_val );
+				break;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_sanitize_val', $retval, $request, $input_type );
+	}
+
+/**
+ * Output the current tab index of a given form
+ *
+ * Use this function to handle the tab indexing of user facing forms within a
+ * template file. Calling this function will automatically increment the global
+ * tab index by default.
+ *
+ * @since 2.0.0 bbPress (r2810)
+ *
+ * @deprecated 2.6.0 bbPress (r5561)
+ *
+ * @link https://bbpress.trac.wordpress.org/attachment/ticket/2714 Trac Ticket
+ * @param int $auto_increment Optional. Default true. Set to false to prevent
+ *                             increment
+ */
+function bbp_tab_index( $auto_increment = true ) {
+	echo bbp_get_tab_index( $auto_increment );
+}
+
+	/**
+	 * Return the current tab index of a given form
+	 *
+	 * Use this function to handle the tab indexing of user facing forms
+	 * within a template file. Calling this function will automatically
+	 * increment the global tab index by default.
+	 *
+	 * @since 2.0.0 bbPress (r2810)
+	 *
+	 * @deprecated 2.6.0 bbPress (r5561)
+	 *
+	 * @link https://bbpress.trac.wordpress.org/attachment/ticket/2714 Trac Ticket
+	 * @param int $auto_increment Optional. Default true. Set to false to
+	 *                             prevent the increment
+	 * @return int $bbp->tab_index The global tab index
+	 */
+	function bbp_get_tab_index( $auto_increment = true ) {
+		$bbp = bbpress();
+
+		if ( true === $auto_increment ) {
+			++$bbp->tab_index;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_tab_index', (int) $bbp->tab_index );
+	}
+
+/**
+ * Output a "tabindex" attribute for an element, if an index was passed.
+ *
+ * This helper function is in use, but it is generally considered impolite to
+ * override the "tabindex" attribute beyond what the browser naturally assigns.
+ *
+ * Most internal usages pass `false` which results in no attribute being used.
+ *
+ * @since 2.6.0 bbPress (r6424)
+ *
+ * @param mixed $tab False to skip, any integer to use
+ */
+function bbp_tab_index_attribute( $tab = false ) {
+	echo bbp_get_tab_index_attribute( $tab );
+}
+
+	/**
+	 * Return a "tabindex" attribute for an element, if an index was passed.
+	 *
+	 * This helper function is in use, but it is generally considered impolite to
+	 * override the "tabindex" attribute beyond what the browser naturally assigns.
+	 *
+	 * Most internal usages pass `false` which results in no attribute being used.
+	 *
+	 * @since 2.6.0 bbPress (r6424)
+	 *
+	 * @param mixed $tab False to skip, any integer to use
+	 *
+	 * @return string
+	 */
+	function bbp_get_tab_index_attribute( $tab = false ) {
+
+		// Get attribute
+		$attr = is_numeric( $tab )
+			? ' tabindex="' . (int) $tab . '"'
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_tab_index_attribute', $attr, $tab );
+	}
+
+/**
+ * Output a select box allowing to pick which forum/topic a new topic/reply
+ * belongs in.
+ *
+ * Can be used for any post type, but is mostly used for topics and forums.
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param array $args See {@link bbp_get_dropdown()} for arguments
+ */
+function bbp_dropdown( $args = array() ) {
+	echo bbp_get_dropdown( $args );
+}
+	/**
+	 * Return a select box allowing to pick which forum/topic a new
+	 * topic/reply belongs in.
+	 *
+	 * @since 2.0.0 bbPress (r2746)
+	 *
+	 * @param array $args The function supports these args:
+	 *  - post_type: Post type, defaults to bbp_get_forum_post_type() (bbp_forum)
+	 *  - selected: Selected ID, to not have any value as selected, pass
+	 *               anything smaller than 0 (due to the nature of select
+	 *               box, the first value would of course be selected -
+	 *               though you can have that as none (pass 'show_none' arg))
+	 *  - orderby: Defaults to 'menu_order title'
+	 *  - post_parent: Post parent. Defaults to 0
+	 *  - post_status: Which all post_statuses to find in? Can be an array
+	 *                  or CSV of publish, category, closed, private, spam,
+	 *                  trash (based on post type) - if not set, these are
+	 *                  automatically determined based on the post_type
+	 *  - posts_per_page: Retrieve all forums/topics. Defaults to -1 to get
+	 *                     all posts
+	 *  - walker: Which walker to use? Defaults to
+	 *             {@link BBP_Walker_Dropdown}
+	 *  - select_id: ID of the select box. Defaults to 'bbp_forum_id'
+	 *  - tab: Deprecated. Tabindex value. False or integer
+	 *  - options_only: Show only <options>? No <select>?
+	 *  - show_none: Boolean or String __( '&mdash; No Forum &mdash;', 'bbpress' )
+	 *  - disable_categories: Disable forum categories and closed forums?
+	 *                         Defaults to true. Only for forums and when
+	 * @return string The dropdown
+	 */
+	function bbp_get_dropdown( $args = array() ) {
+
+		// Setup return value
+		$retval = '';
+
+		/** Arguments *********************************************************/
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+
+			// Support for get_posts()
+			'post_type'          => bbp_get_forum_post_type(),
+			'post_parent'        => null,
+			'post_status'        => null,
+			'selected'           => 0,
+			'include'            => array(),
+			'exclude'            => array(),
+			'numberposts'        => -1,
+			'orderby'            => 'menu_order title',
+			'order'              => 'ASC',
+
+			// Preloaded content
+			'posts'              => array(),
+
+			// Custom hierarchy walkers
+			'walker'             => '',
+
+			// Output-related
+			'select_id'          => 'bbp_forum_id',
+			'select_class'       => 'bbp_dropdown',
+			'tab'                => false,
+			'options_only'       => false,
+			'show_none'          => false,
+			'disable_categories' => true,
+			'disabled'           => ''
+		), 'get_dropdown' );
+
+		// Fallback to our walker
+		if ( empty( $r['walker'] ) ) {
+			$r['walker']            = new BBP_Walker_Dropdown();
+			$r['walker']->tree_type = $r['post_type'];
+		}
+
+		// Force 0
+		if ( is_numeric( $r['selected'] ) && ( $r['selected'] < 0 ) ) {
+			$r['selected'] = 0;
+		}
+
+		// Force array
+		if ( ! empty( $r['include'] ) && ! is_array( $r['include'] ) ) {
+			$r['include'] = explode( ',', $r['include'] );
+		}
+
+		// Force array
+		if ( ! empty( $r['exclude'] ) && ! is_array( $r['exclude'] ) ) {
+			$r['exclude'] = explode( ',', $r['exclude'] );
+		}
+
+		/** Setup Posts *******************************************************/
+
+		/**
+		 * Allow passing of custom posts data
+		 *
+		 * @see bbp_get_reply_to_dropdown() as an example
+		 */
+		if ( empty( $r['posts'] ) ) {
+			$r['posts'] = get_posts( array(
+				'post_type'   => $r['post_type'],
+				'post_status' => $r['post_status'],
+				'post_parent' => $r['post_parent'],
+				'include'     => $r['include'],
+				'exclude'     => $r['exclude'],
+				'numberposts' => $r['numberposts'],
+				'orderby'     => $r['orderby'],
+				'order'       => $r['order'],
+			) );
+		}
+
+		/** Drop Down *********************************************************/
+
+		// Build the opening tag for the select element
+		if ( empty( $r['options_only'] ) ) {
+
+			// Should this select appear disabled?
+			$disabled  = disabled( isset( bbpress()->options[ $r['disabled'] ] ), true, false );
+
+			// Setup the tab index attribute
+			$tab       = ! empty( $r['tab'] ) ? ' tabindex="' . intval( $r['tab'] ) . '"' : '';
+
+			// Open the select tag
+			$retval   .= '<select name="' . esc_attr( $r['select_id'] ) . '" id="' . esc_attr( $r['select_id'] ) . '" class="' . esc_attr( $r['select_class'] ) . '"' . $disabled . $tab . '>' . "\n";
+		}
+
+		// Display a leading 'no-value' option, with or without custom text
+		if ( ! empty( $r['show_none'] ) || ! empty( $r['none_found'] ) ) {
+
+			// Open the 'no-value' option tag
+			$retval .= "\t<option value=\"\" class=\"level-0\">";
+
+			// Use deprecated 'none_found' first for backpat
+			if ( ! empty( $r['none_found'] ) && is_string( $r['none_found'] ) ) {
+				$retval .= esc_html( $r['none_found'] );
+
+			// Use 'show_none' second
+			} elseif ( ! empty( $r['show_none'] ) && is_string( $r['show_none'] ) ) {
+				$retval .= esc_html( $r['show_none'] );
+
+			// Otherwise, make some educated guesses
+			} else {
+
+				// Switch the response based on post type
+				switch ( $r['post_type'] ) {
+
+					// Topics
+					case bbp_get_topic_post_type() :
+						$retval .= esc_html__( 'No topics available', 'bbpress' );
+						break;
+
+					// Forums
+					case bbp_get_forum_post_type() :
+						$retval .= esc_html__( 'No forums available', 'bbpress' );
+						break;
+
+					// Any other
+					default :
+						$retval .= esc_html__( 'None available', 'bbpress' );
+						break;
+				}
+			}
+
+			// Close the 'no-value' option tag
+			$retval .= '</option>';
+		}
+
+		// Items found so walk the tree
+		if ( ! empty( $r['posts'] ) ) {
+			$retval .= walk_page_dropdown_tree( $r['posts'], 0, $r );
+		}
+
+		// Close the selecet tag
+		if ( empty( $r['options_only'] ) ) {
+			$retval .= '</select>';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_dropdown', $retval, $r, $args );
+	}
+
+/**
+ * Output the required hidden fields when creating/editing a forum
+ *
+ * @since 2.1.0 bbPress (r3553)
+ */
+function bbp_forum_form_fields() {
+
+	if ( bbp_is_forum_edit() ) : ?>
+
+		<input type="hidden" name="action"       id="bbp_post_action" value="bbp-edit-forum" />
+		<input type="hidden" name="bbp_forum_id" id="bbp_forum_id"    value="<?php bbp_forum_id(); ?>" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-forum_' . bbp_get_forum_id(), '_bbp_unfiltered_html_forum', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-edit-forum_' . bbp_get_forum_id() );
+
+	else :
+
+		if ( bbp_is_single_forum() ) : ?>
+
+			<input type="hidden" name="bbp_forum_parent_id" id="bbp_forum_parent_id" value="<?php bbp_forum_parent_id(); ?>" />
+
+		<?php endif; ?>
+
+		<input type="hidden" name="action" id="bbp_post_action" value="bbp-new-forum" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-forum_new', '_bbp_unfiltered_html_forum', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-new-forum' );
+
+	endif;
+}
+
+/**
+ * Output the required hidden fields when creating/editing a topic
+ *
+ * @since 2.0.0 bbPress (r2753)
+ */
+function bbp_topic_form_fields() {
+
+	if ( bbp_is_topic_edit() ) : ?>
+
+		<input type="hidden" name="action"       id="bbp_post_action" value="bbp-edit-topic" />
+		<input type="hidden" name="bbp_topic_id" id="bbp_topic_id"    value="<?php bbp_topic_id(); ?>" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-topic_' . bbp_get_topic_id(), '_bbp_unfiltered_html_topic', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-edit-topic_' . bbp_get_topic_id() );
+
+	else :
+
+		if ( bbp_is_single_forum() ) : ?>
+
+			<input type="hidden" name="bbp_forum_id" id="bbp_forum_id" value="<?php bbp_forum_id(); ?>" />
+
+		<?php endif; ?>
+
+		<input type="hidden" name="action" id="bbp_post_action" value="bbp-new-topic" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-topic_new', '_bbp_unfiltered_html_topic', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-new-topic' );
+
+	endif;
+}
+
+/**
+ * Output the required hidden fields when creating/editing a reply
+ *
+ * @since 2.0.0 bbPress (r2753)
+ */
+function bbp_reply_form_fields() {
+
+	if ( bbp_is_reply_edit() ) : ?>
+
+		<input type="hidden" name="bbp_reply_id"    id="bbp_reply_id"    value="<?php bbp_reply_id(); ?>" />
+		<input type="hidden" name="action"          id="bbp_post_action" value="bbp-edit-reply" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-reply_' . bbp_get_reply_id(), '_bbp_unfiltered_html_reply', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-edit-reply_' . bbp_get_reply_id() );
+
+	else : ?>
+
+		<input type="hidden" name="bbp_topic_id"    id="bbp_topic_id"    value="<?php bbp_topic_id(); ?>" />
+		<input type="hidden" name="bbp_reply_to"    id="bbp_reply_to"    value="<?php bbp_form_reply_to(); ?>" />
+		<input type="hidden" name="action"          id="bbp_post_action" value="bbp-new-reply" />
+
+		<?php if ( current_user_can( 'unfiltered_html' ) ) :
+			wp_nonce_field( 'bbp-unfiltered-html-reply_' . bbp_get_topic_id(), '_bbp_unfiltered_html_reply', false );
+		endif; ?>
+
+		<?php wp_nonce_field( 'bbp-new-reply' );
+
+		// Show redirect field if not viewing a specific topic
+		if ( bbp_is_query_name( 'bbp_single_topic' ) ) :
+			$redirect_to = apply_filters( 'bbp_reply_form_redirect_to', get_permalink() );
+			bbp_redirect_to_field( $redirect_to );
+		endif;
+	endif;
+}
+
+/**
+ * Output the required hidden fields when editing a user
+ *
+ * @since 2.0.0 bbPress (r2690)
+ */
+function bbp_edit_user_form_fields() {
+?>
+
+	<input type="hidden" name="action"  id="bbp_post_action" value="bbp-update-user" />
+	<input type="hidden" name="user_id" id="user_id"         value="<?php bbp_displayed_user_id(); ?>" />
+
+	<?php wp_nonce_field( 'update-user_' . bbp_get_displayed_user_id() );
+}
+
+/**
+ * Merge topic form fields
+ *
+ * Output the required hidden fields when merging a topic
+ *
+ * @since 2.0.0 bbPress (r2756)
+ */
+function bbp_merge_topic_form_fields() {
+?>
+
+	<input type="hidden" name="action"       id="bbp_post_action" value="bbp-merge-topic" />
+	<input type="hidden" name="bbp_topic_id" id="bbp_topic_id"    value="<?php bbp_topic_id(); ?>" />
+
+	<?php wp_nonce_field( 'bbp-merge-topic_' . bbp_get_topic_id() );
+}
+
+/**
+ * Split topic form fields
+ *
+ * Output the required hidden fields when splitting a topic
+ *
+ * @since 2.0.0 bbPress (r2756)
+ */
+function bbp_split_topic_form_fields() {
+?>
+
+	<input type="hidden" name="action"       id="bbp_post_action" value="bbp-split-topic" />
+	<input type="hidden" name="bbp_reply_id" id="bbp_reply_id"    value="<?php echo intval( $_GET['reply_id'] ); ?>" />
+
+	<?php wp_nonce_field( 'bbp-split-topic_' . bbp_get_topic_id() );
+}
+
+/**
+ * Move reply form fields
+ *
+ * Output the required hidden fields when moving a reply
+ */
+function bbp_move_reply_form_fields() {
+?>
+
+	<input type="hidden" name="action"       id="bbp_post_action" value="bbp-move-reply" />
+	<input type="hidden" name="bbp_reply_id" id="bbp_reply_id"    value="<?php echo intval( $_GET['reply_id'] ); ?>" />
+
+	<?php wp_nonce_field( 'bbp-move-reply_' . bbp_get_reply_id() );
+}
+
+/**
+ * Output a textarea or TinyMCE if enabled
+ *
+ * @since 2.1.0 bbPress (r3586)
+ *
+ * @param array $args
+ */
+function bbp_the_content( $args = array() ) {
+	echo bbp_get_the_content( $args );
+}
+	/**
+	 * Return a textarea or TinyMCE if enabled
+	 *
+	 * @since 2.1.0 bbPress (r3586)
+	 *
+	 * @param array $args
+	 *
+	 * @return string HTML from output buffer
+	 */
+	function bbp_get_the_content( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'context'           => 'topic',
+			'before'            => '<div class="bbp-the-content-wrapper">',
+			'after'             => '</div>',
+			'wpautop'           => true,
+			'media_buttons'     => false,
+			'textarea_rows'     => '12',
+			'tabindex'          => false,
+			'tabfocus_elements' => 'bbp_topic_title,bbp_topic_tags',
+			'editor_class'      => 'bbp-the-content',
+			'tinymce'           => false,
+			'teeny'             => true,
+			'quicktags'         => true,
+			'dfw'               => false
+		), 'get_the_content' );
+
+		// If using tinymce, remove our escaping and trust tinymce
+		if ( bbp_use_wp_editor() && ( false !== $r['tinymce'] ) ) {
+			remove_filter( 'bbp_get_form_forum_content', 'esc_textarea' );
+			remove_filter( 'bbp_get_form_topic_content', 'esc_textarea' );
+			remove_filter( 'bbp_get_form_reply_content', 'esc_textarea' );
+		}
+
+		// Assume we are not editing
+		$post_content = call_user_func( 'bbp_get_form_' . $r['context'] . '_content' );
+
+		// Start an output buffor
+		ob_start();
+
+		// Output something before the editor
+		if ( ! empty( $r['before'] ) ) {
+			echo $r['before'];
+		}
+
+		// Use TinyMCE if available
+		if ( bbp_use_wp_editor() ) :
+
+			// Enable additional TinyMCE plugins before outputting the editor
+			add_filter( 'tiny_mce_plugins',   'bbp_get_tiny_mce_plugins'   );
+			add_filter( 'teeny_mce_plugins',  'bbp_get_tiny_mce_plugins'   );
+			add_filter( 'teeny_mce_buttons',  'bbp_get_teeny_mce_buttons'  );
+			add_filter( 'quicktags_settings', 'bbp_get_quicktags_settings' );
+
+			// Output the editor
+			wp_editor( $post_content, 'bbp_' . $r['context'] . '_content', array(
+				'wpautop'           => $r['wpautop'],
+				'media_buttons'     => $r['media_buttons'],
+				'textarea_rows'     => $r['textarea_rows'],
+				'tabindex'          => $r['tabindex'],
+				'tabfocus_elements' => $r['tabfocus_elements'],
+				'editor_class'      => $r['editor_class'],
+				'tinymce'           => $r['tinymce'],
+				'teeny'             => $r['teeny'],
+				'quicktags'         => $r['quicktags'],
+				'dfw'               => $r['dfw'],
+			) );
+
+			// Remove additional TinyMCE plugins after outputting the editor
+			remove_filter( 'tiny_mce_plugins',   'bbp_get_tiny_mce_plugins'   );
+			remove_filter( 'teeny_mce_plugins',  'bbp_get_tiny_mce_plugins'   );
+			remove_filter( 'teeny_mce_buttons',  'bbp_get_teeny_mce_buttons'  );
+			remove_filter( 'quicktags_settings', 'bbp_get_quicktags_settings' );
+
+		/**
+		 * Fallback to normal textarea.
+		 *
+		 * Note that we do not use esc_textarea() here to prevent double
+		 * escaping the editable output, mucking up existing content.
+		 */
+		else : ?>
+
+			<textarea id="bbp_<?php echo esc_attr( $r['context'] ); ?>_content" class="<?php echo esc_attr( $r['editor_class'] ); ?>" name="bbp_<?php echo esc_attr( $r['context'] ); ?>_content" cols="60" rows="<?php echo esc_attr( $r['textarea_rows'] ); ?>" <?php bbp_tab_index_attribute( $r['tabindex'] ); ?>><?php echo $post_content; ?></textarea>
+
+		<?php endif;
+
+		// Output something after the editor
+		if ( ! empty( $r['after'] ) ) {
+			echo $r['after'];
+		}
+
+		// Put the output into a usable variable
+		$output = ob_get_clean();
+
+		// Filter & return
+		return apply_filters( 'bbp_get_the_content', $output, $args, $post_content );
+	}
+
+/**
+ * Edit TinyMCE plugins to match core behaviour
+ *
+ * @since 2.3.0 bbPress (r4574)
+ *
+ * @param array $plugins
+ * @see tiny_mce_plugins, teeny_mce_plugins
+ * @return array
+ */
+function bbp_get_tiny_mce_plugins( $plugins = array() ) {
+
+	// Unset fullscreen
+	foreach ( $plugins as $key => $value ) {
+		if ( 'fullscreen' === $value ) {
+			unset( $plugins[ $key ] );
+			break;
+		}
+	}
+
+	// Add the tabfocus plugin
+	$plugins[] = 'tabfocus';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_tiny_mce_plugins', $plugins );
+}
+
+/**
+ * Edit TeenyMCE buttons to match allowedtags
+ *
+ * @since 2.3.0 bbPress (r4605)
+ *
+ * @param array $buttons
+ * @see teeny_mce_buttons
+ * @return array
+ */
+function bbp_get_teeny_mce_buttons( $buttons = array() ) {
+
+	// Remove some buttons from TeenyMCE
+	$buttons = array_diff( $buttons, array(
+		'underline',
+		'justifyleft',
+		'justifycenter',
+		'justifyright'
+	) );
+
+	// Images
+	array_push( $buttons, 'image' );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_teeny_mce_buttons', $buttons );
+}
+
+/**
+ * Edit TinyMCE quicktags buttons to match allowedtags
+ *
+ * @since 2.3.0 bbPress (r4606)
+ *
+ * @param array $settings
+ * @see quicktags_settings
+ * @return array Quicktags settings
+ */
+function bbp_get_quicktags_settings( $settings = array() ) {
+
+	// Get buttons out of settings
+	$buttons_array = explode( ',', $settings['buttons'] );
+
+	// Diff the ones we don't want out
+	$buttons = array_diff( $buttons_array, array(
+		'ins',
+		'more',
+		'spell'
+	) );
+
+	// Put them back into a string in the $settings array
+	$settings['buttons'] = implode( ',', $buttons );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_quicktags_settings', $settings );
+}
+
+/** Views *********************************************************************/
+
+/**
+ * Output the view id
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view Optional. View id
+ */
+function bbp_view_id( $view = '' ) {
+	echo bbp_get_view_id( $view );
+}
+
+	/**
+	 * Get the view id
+	 *
+	 * Use view id if supplied, otherwise bbp_get_view_rewrite_id() query var.
+	 *
+	 * @since 2.0.0 bbPress (r2789)
+	 *
+	 * @param string $view Optional. View id.
+	 * @return bool|string ID on success, false on failure
+	 */
+	function bbp_get_view_id( $view = '' ) {
+		$bbp = bbpress();
+
+		// User supplied string
+		if ( ! empty( $view ) ) {
+			$view_id = sanitize_key( $view );
+
+		// Current view ID
+		} elseif ( ! empty( $bbp->current_view_id ) ) {
+			$view_id = $bbp->current_view_id;
+
+		// Querying for view
+		} else {
+			$view_id = get_query_var( bbp_get_view_rewrite_id() );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_view_id', $view_id, $view );
+	}
+
+/**
+ * Output the view name aka title
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view Optional. View id
+ */
+function bbp_view_title( $view = '' ) {
+	echo bbp_get_view_title( $view );
+}
+
+	/**
+	 * Get the view name aka title
+	 *
+	 * If a view id is supplied, that is used. Otherwise the bbp_view
+	 * query var is checked for.
+	 *
+	 * @since 2.0.0 bbPress (r2789)
+	 *
+	 * @param string $view Optional. View id
+	 * @return bool|string Title on success, false on failure
+	 */
+	function bbp_get_view_title( $view = '' ) {
+		$bbp = bbpress();
+
+		$view = bbp_get_view_id( $view );
+		if ( empty( $view ) ) {
+			return false;
+		}
+
+		return $bbp->views[ $view ]['title'];
+	}
+
+/**
+ * Output the view url
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view Optional. View id
+ */
+function bbp_view_url( $view = false ) {
+	echo esc_url( bbp_get_view_url( $view ) );
+}
+	/**
+	 * Return the view url
+	 *
+	 * @since 2.0.0 bbPress (r2789)
+	 *
+	 * @param string $view Optional. View id
+	 *                        used view id
+	 * @return string View url (or home url if the view was not found)
+	 */
+	function bbp_get_view_url( $view = false ) {
+
+		$view = bbp_get_view_id( $view );
+		if ( empty( $view ) ) {
+			return home_url();
+		}
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Run through home_url()
+			$url = trailingslashit( bbp_get_root_url() . bbp_get_view_slug() ) . $view;
+			$url = user_trailingslashit( $url );
+			$url = home_url( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_view_rewrite_id() => $view
+			), home_url( '/' ) );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_view_link', $url, $view );
+	}
+
+/** Query *********************************************************************/
+
+/**
+ * Check the passed parameter against the current _bbp_query_name
+ *
+ * @since 2.0.0 bbPress (r2980)
+ *
+ * @return bool True if match, false if not
+ */
+function bbp_is_query_name( $name = '' )  {
+	return (bool) ( bbp_get_query_name() === $name );
+}
+
+/**
+ * Get the '_bbp_query_name' setting
+ *
+ * @since 2.0.0 bbPress (r2695)
+ *
+ * @return string To return the query var value
+ */
+function bbp_get_query_name()  {
+	return get_query_var( '_bbp_query_name' );
+}
+
+/**
+ * Set the '_bbp_query_name' setting to $name
+ *
+ * @since 2.0.0 bbPress (r2692)
+ *
+ * @param string $name What to set the query var to
+ */
+function bbp_set_query_name( $name = '' )  {
+	set_query_var( '_bbp_query_name', $name );
+}
+
+/**
+ * Used to clear the '_bbp_query_name' setting
+ *
+ * @since 2.0.0 bbPress (r2692)
+ *
+ */
+function bbp_reset_query_name() {
+	bbp_set_query_name();
+}
+
+/** Breadcrumbs ***************************************************************/
+
+/**
+ * Output the page title as a breadcrumb
+ *
+ * @since 2.0.0 bbPress (r2589)
+ *
+ * @param string $sep Separator. Defaults to '&larr;'
+ * @param bool $current_page Include the current item
+ * @param bool $root Include the root page if one exists
+ */
+function bbp_title_breadcrumb( $args = array() ) {
+	echo bbp_get_breadcrumb( $args );
+}
+
+/**
+ * Output a breadcrumb
+ *
+ * @since 2.0.0 bbPress (r2589)
+ *
+ * @param string $sep Separator. Defaults to '&larr;'
+ * @param bool $current_page Include the current item
+ * @param bool $root Include the root page if one exists
+ */
+function bbp_breadcrumb( $args = array() ) {
+	echo bbp_get_breadcrumb( $args );
+}
+	/**
+	 * Return a breadcrumb ( forum -> topic -> reply )
+	 *
+	 * @since 2.0.0 bbPress (r2589)
+	 *
+	 * @param string $sep Separator. Defaults to '&larr;'
+	 * @param bool $current_page Include the current item
+	 * @param bool $root Include the root page if one exists
+	 *
+	 * @return string Breadcrumbs
+	 */
+	function bbp_get_breadcrumb( $args = array() ) {
+
+		// Turn off breadcrumbs
+		if ( apply_filters( 'bbp_no_breadcrumb', is_front_page() ) ) {
+			return;
+		}
+
+		// Define variables
+		$front_id         = $root_id                                 = 0;
+		$ancestors        = $crumbs           = $tag_data            = array();
+		$pre_root_text    = $pre_front_text   = $pre_current_text    = '';
+		$pre_include_root = $pre_include_home = $pre_include_current = true;
+
+		/** Home Text *********************************************************/
+
+		// No custom home text
+		if ( empty( $args['home_text'] ) ) {
+
+			$front_id = get_option( 'page_on_front' );
+
+			// Set home text to page title
+			if ( ! empty( $front_id ) ) {
+				$pre_front_text = get_the_title( $front_id );
+
+			// Default to 'Home'
+			} else {
+				$pre_front_text = esc_html__( 'Home', 'bbpress' );
+			}
+		}
+
+		/** Root Text *********************************************************/
+
+		// No custom root text
+		if ( empty( $args['root_text'] ) ) {
+			$page = bbp_get_page_by_path( bbp_get_root_slug() );
+			if ( ! empty( $page ) ) {
+				$root_id = $page->ID;
+			}
+			$pre_root_text = bbp_get_forum_archive_title();
+		}
+
+		/** Includes **********************************************************/
+
+		// Root slug is also the front page
+		if ( ! empty( $front_id ) && ( $front_id === $root_id ) ) {
+			$pre_include_root = false;
+		}
+
+		// Don't show root if viewing forum archive
+		if ( bbp_is_forum_archive() ) {
+			$pre_include_root = false;
+		}
+
+		// Don't show root if viewing page in place of forum archive
+		if ( ! empty( $root_id ) && ( ( is_single() || is_page() ) && ( $root_id === get_the_ID() ) ) ) {
+			$pre_include_root = false;
+		}
+
+		/** Current Text ******************************************************/
+
+		// Search page
+		if ( bbp_is_search() ) {
+			$pre_current_text = bbp_get_search_title();
+
+		// Forum archive
+		} elseif ( bbp_is_forum_archive() ) {
+			$pre_current_text = bbp_get_forum_archive_title();
+
+		// Topic archive
+		} elseif ( bbp_is_topic_archive() ) {
+			$pre_current_text = bbp_get_topic_archive_title();
+
+		// View
+		} elseif ( bbp_is_single_view() ) {
+			$pre_current_text = bbp_get_view_title();
+
+		// Single Forum
+		} elseif ( bbp_is_single_forum() ) {
+			$pre_current_text = bbp_get_forum_title();
+
+		// Single Topic
+		} elseif ( bbp_is_single_topic() ) {
+			$pre_current_text = bbp_get_topic_title();
+
+		// Single Topic
+		} elseif ( bbp_is_single_reply() ) {
+			$pre_current_text = bbp_get_reply_title();
+
+		// Topic Tag (or theme compat topic tag)
+		} elseif ( bbp_is_topic_tag() || ( get_query_var( 'bbp_topic_tag' ) && ! bbp_is_topic_tag_edit() ) ) {
+
+			// Always include the tag name
+			$tag_data[] = bbp_get_topic_tag_name();
+
+			// If capable, include a link to edit the tag
+			if ( current_user_can( 'manage_topic_tags' ) ) {
+				$tag_data[] = '<a href="' . esc_url( bbp_get_topic_tag_edit_link() ) . '" class="bbp-edit-topic-tag-link">' . esc_html__( '(Edit)', 'bbpress' ) . '</a>';
+			}
+
+			// Implode the results of the tag data
+			$pre_current_text = sprintf( esc_html__( 'Topic Tag: %s', 'bbpress' ), implode( ' ', $tag_data ) );
+
+		// Edit Topic Tag
+		} elseif ( bbp_is_topic_tag_edit() ) {
+			$pre_current_text = esc_html__( 'Edit', 'bbpress' );
+
+		// Single
+		} else {
+			$pre_current_text = get_the_title();
+		}
+
+		/** Parse Args ********************************************************/
+
+		// Parse args
+		$r = bbp_parse_args( $args, array(
+
+			// HTML
+			'before'          => '<div class="bbp-breadcrumb"><p>',
+			'after'           => '</p></div>',
+
+			// Separator
+			'sep'             => is_rtl() ? __( '&lsaquo;', 'bbpress' ) : __( '&rsaquo;', 'bbpress' ),
+			'pad_sep'         => 1,
+			'sep_before'      => '<span class="bbp-breadcrumb-sep">',
+			'sep_after'       => '</span>',
+
+			// Crumbs
+			'crumb_before'    => '',
+			'crumb_after'     => '',
+
+			// Home
+			'include_home'    => $pre_include_home,
+			'home_text'       => $pre_front_text,
+
+			// Forum root
+			'include_root'    => $pre_include_root,
+			'root_text'       => $pre_root_text,
+
+			// Current
+			'include_current' => $pre_include_current,
+			'current_text'    => $pre_current_text,
+			'current_before'  => '<span class="bbp-breadcrumb-current">',
+			'current_after'   => '</span>',
+		), 'get_breadcrumb' );
+
+		/** Ancestors *********************************************************/
+
+		// Get post ancestors
+		if ( is_singular() || bbp_is_forum_edit() || bbp_is_topic_edit() || bbp_is_reply_edit() ) {
+			$ancestors = array_reverse( (array) get_post_ancestors( get_the_ID() ) );
+		}
+
+		// Do we want to include a link to home?
+		if ( ! empty( $r['include_home'] ) || empty( $r['home_text'] ) ) {
+			$crumbs[] = '<a href="' . esc_url( home_url() ) . '" class="bbp-breadcrumb-home">' . $r['home_text'] . '</a>';
+		}
+
+		// Do we want to include a link to the forum root?
+		if ( ! empty( $r['include_root'] ) || empty( $r['root_text'] ) ) {
+
+			// Page exists at root slug path, so use its permalink
+			$page = bbp_get_page_by_path( bbp_get_root_slug() );
+			if ( ! empty( $page ) ) {
+				$root_url = get_permalink( $page->ID );
+
+			// Use the root slug
+			} else {
+				$root_url = get_post_type_archive_link( bbp_get_forum_post_type() );
+			}
+
+			// Add the breadcrumb
+			$crumbs[] = '<a href="' . esc_url( $root_url ) . '" class="bbp-breadcrumb-root">' . $r['root_text'] . '</a>';
+		}
+
+		// Ancestors exist
+		if ( ! empty( $ancestors ) ) {
+
+			// Loop through parents
+			foreach ( (array) $ancestors as $parent_id ) {
+
+				// Parents
+				$parent = get_post( $parent_id );
+
+				// Skip parent if empty or error
+				if ( empty( $parent ) || is_wp_error( $parent ) ) {
+					continue;
+				}
+
+				// Switch through post_type to ensure correct filters are applied
+				switch ( $parent->post_type ) {
+
+					// Forum
+					case bbp_get_forum_post_type() :
+						$crumbs[] = '<a href="' . esc_url( bbp_get_forum_permalink( $parent->ID ) ) . '" class="bbp-breadcrumb-forum">' . bbp_get_forum_title( $parent->ID ) . '</a>';
+						break;
+
+					// Topic
+					case bbp_get_topic_post_type() :
+						$crumbs[] = '<a href="' . esc_url( bbp_get_topic_permalink( $parent->ID ) ) . '" class="bbp-breadcrumb-topic">' . bbp_get_topic_title( $parent->ID ) . '</a>';
+						break;
+
+					// Reply (Note: not in most themes)
+					case bbp_get_reply_post_type() :
+						$crumbs[] = '<a href="' . esc_url( bbp_get_reply_permalink( $parent->ID ) ) . '" class="bbp-breadcrumb-reply">' . bbp_get_reply_title( $parent->ID ) . '</a>';
+						break;
+
+					// WordPress Post/Page/Other
+					default :
+						$crumbs[] = '<a href="' . esc_url( get_permalink( $parent->ID ) ) . '" class="bbp-breadcrumb-item">' . get_the_title( $parent->ID ) . '</a>';
+						break;
+				}
+			}
+
+		// Edit topic tag
+		} elseif ( bbp_is_topic_tag_edit() ) {
+			$crumbs[] = '<a href="' . esc_url( get_term_link( bbp_get_topic_tag_id(), bbp_get_topic_tag_tax_id() ) ) . '" class="bbp-breadcrumb-topic-tag">' . sprintf( esc_html__( 'Topic Tag: %s', 'bbpress' ), bbp_get_topic_tag_name() ) . '</a>';
+
+		// Search
+		} elseif ( bbp_is_search() && bbp_get_search_terms() ) {
+			$crumbs[] = '<a href="' . esc_url( bbp_get_search_url() ) . '" class="bbp-breadcrumb-search">' . esc_html__( 'Search', 'bbpress' ) . '</a>';
+		}
+
+		/** Current ***********************************************************/
+
+		// Add current page to breadcrumb
+		if ( ! empty( $r['include_current'] ) || empty( $r['current_text'] ) ) {
+			$crumbs[] = $r['current_before'] . $r['current_text'] . $r['current_after'];
+		}
+
+		/** Separator *********************************************************/
+
+		// Wrap the separator in before/after before padding and filter
+		if ( ! empty( $r['sep'] ) ) {
+			$sep = $r['sep_before'] . $r['sep'] . $r['sep_after'];
+		} else {
+			$sep = '';
+		}
+
+		// Pad the separator
+		if ( ! empty( $r['pad_sep'] ) ) {
+			if ( function_exists( 'mb_strlen' ) ) {
+				$sep = str_pad( $sep, mb_strlen( $sep ) + ( (int) $r['pad_sep'] * 2 ), ' ', STR_PAD_BOTH );
+			} else {
+				$sep = str_pad( $sep, strlen( $sep ) + ( (int) $r['pad_sep'] * 2 ), ' ', STR_PAD_BOTH );
+			}
+		}
+
+		/** Finish Up *********************************************************/
+
+		// Filter the separator and breadcrumb
+		$sep    = apply_filters( 'bbp_breadcrumb_separator', $sep    );
+		$crumbs = apply_filters( 'bbp_breadcrumbs',          $crumbs );
+
+		// Build the trail
+		$trail  = ! empty( $crumbs ) ? ( $r['before'] . $r['crumb_before'] . implode( $sep . $r['crumb_after'] . $r['crumb_before'] , $crumbs ) . $r['crumb_after'] . $r['after'] ) : '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_breadcrumb', $trail, $crumbs, $r, $args );
+	}
+
+/** Topic Tags ***************************************************************/
+
+/**
+ * Output all of the allowed tags in HTML format with attributes.
+ *
+ * This is useful for displaying in the post area, which elements and
+ * attributes are supported. As well as any plugins which want to display it.
+ *
+ * @since 2.0.0 bbPress (r2780)
+ */
+function bbp_allowed_tags() {
+	echo bbp_get_allowed_tags();
+}
+	/**
+	 * Display all of the allowed tags in HTML format with attributes.
+	 *
+	 * This is useful for displaying in the post area, which elements and
+	 * attributes are supported. As well as any plugins which want to display it.
+	 *
+	 * @since 2.0.0 bbPress (r2780)
+	 *
+	 * @return string HTML allowed tags entity encoded.
+	 */
+	function bbp_get_allowed_tags() {
+
+		$allowed = '';
+
+		foreach ( (array) bbp_kses_allowed_tags() as $tag => $attributes ) {
+			$allowed .= '<' . $tag;
+			if ( 0 < count( $attributes ) ) {
+				foreach ( array_keys( $attributes ) as $attribute ) {
+					$allowed .= ' ' . $attribute . '=""';
+				}
+			}
+			$allowed .= '> ';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_allowed_tags', htmlentities( $allowed ) );
+	}
+
+/** Errors & Messages *********************************************************/
+
+/**
+ * Display possible errors & messages inside a template file
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_template_notices() {
+
+	// Bail if no notices or errors
+	if ( ! bbp_has_errors() ) {
+		return;
+	}
+
+	// Define local variable(s)
+	$errors = $messages = array();
+
+	// Get bbPress
+	$bbp = bbpress();
+
+	// Loop through notices
+	foreach ( $bbp->errors->get_error_codes() as $code ) {
+
+		// Get notice severity
+		$severity = $bbp->errors->get_error_data( $code );
+
+		// Loop through notices and separate errors from messages
+		foreach ( $bbp->errors->get_error_messages( $code ) as $error ) {
+			if ( 'message' === $severity ) {
+				$messages[] = $error;
+			} else {
+				$errors[]   = $error;
+			}
+		}
+	}
+
+	// Display errors first...
+	if ( ! empty( $errors ) ) : ?>
+
+		<div class="bbp-template-notice error" role="alert" tabindex="-1">
+			<ul>
+				<li><?php echo implode( "</li>\n<li>", $errors ); ?></li>
+			</ul>
+		</div>
+
+	<?php endif;
+
+	// ...and messages last
+	if ( ! empty( $messages ) ) : ?>
+
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php echo implode( "</li>\n<li>", $messages ); ?></li>
+			</ul>
+		</div>
+
+	<?php endif;
+}
+
+/** Login/logout/register/lost pass *******************************************/
+
+/**
+ * Output the logout link
+ *
+ * @since 2.0.0 bbPress (r2827)
+ *
+ * @param string $redirect_to Redirect to url
+ */
+function bbp_logout_link( $redirect_to = '' ) {
+	echo bbp_get_logout_link( $redirect_to );
+}
+	/**
+	 * Return the logout link
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 *
+	 * @param string $redirect_to Redirect to url
+	 *                        redirect to url
+	 * @return string The logout link
+	 */
+	function bbp_get_logout_link( $redirect_to = '' ) {
+
+		// Build the link
+		$link = '<a href="' . wp_logout_url( $redirect_to ) . '" class="button logout-link">' . esc_html__( 'Log Out', 'bbpress' ) . '</a>';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_logout_link', $link, $redirect_to );
+	}
+
+/** Title *********************************************************************/
+
+/**
+ * Custom page title for bbPress pages
+ *
+ * @since 2.0.0 bbPress (r2788)
+ *
+ * @param string $title Optional. The title (not used).
+ * @param string $sep Optional, default is '&raquo;'. How to separate the
+ *                     various items within the page title.
+ * @param string $seplocation Optional. Direction to display title, 'right'.
+ *                        separator and separator location
+ * @return string The title
+ */
+function bbp_title( $title = '', $sep = '&raquo;', $seplocation = '' ) {
+
+	// Title array
+	$new_title = array();
+
+	/** Archives **************************************************************/
+
+	// Forum Archive
+	if ( bbp_is_forum_archive() ) {
+		$new_title['text'] = bbp_get_forum_archive_title();
+
+	// Topic Archive
+	} elseif ( bbp_is_topic_archive() ) {
+		$new_title['text'] = bbp_get_topic_archive_title();
+
+	/** Edit ******************************************************************/
+
+	// Forum edit page
+	} elseif ( bbp_is_forum_edit() ) {
+		$new_title['text']   = bbp_get_forum_title();
+		$new_title['format'] = esc_attr__( 'Forum Edit: %s', 'bbpress' );
+
+	// Topic edit page
+	} elseif ( bbp_is_topic_edit() ) {
+		$new_title['text']   = bbp_get_topic_title();
+		$new_title['format'] = esc_attr__( 'Topic Edit: %s', 'bbpress' );
+
+	// Reply edit page
+	} elseif ( bbp_is_reply_edit() ) {
+		$new_title['text']   = bbp_get_reply_title();
+		$new_title['format'] = esc_attr__( 'Reply Edit: %s', 'bbpress' );
+
+	// Topic tag edit page
+	} elseif ( bbp_is_topic_tag_edit() ) {
+		$new_title['text']   = bbp_get_topic_tag_name();
+		$new_title['format'] = esc_attr__( 'Topic Tag Edit: %s', 'bbpress' );
+
+	/** Singles ***************************************************************/
+
+	// Forum page
+	} elseif ( bbp_is_single_forum() ) {
+		$new_title['text']   = bbp_get_forum_title();
+		$new_title['format'] = esc_attr__( 'Forum: %s', 'bbpress' );
+
+	// Topic page
+	} elseif ( bbp_is_single_topic() ) {
+		$new_title['text']   = bbp_get_topic_title();
+		$new_title['format'] = esc_attr__( 'Topic: %s', 'bbpress' );
+
+	// Replies
+	} elseif ( bbp_is_single_reply() ) {
+		$new_title['text']   = bbp_get_reply_title();
+
+	// Topic tag page
+	} elseif ( bbp_is_topic_tag() || get_query_var( 'bbp_topic_tag' ) ) {
+		$new_title['text']   = bbp_get_topic_tag_name();
+		$new_title['format'] = esc_attr__( 'Topic Tag: %s', 'bbpress' );
+
+	/** Users *****************************************************************/
+
+	// Profile page
+	} elseif ( bbp_is_single_user() ) {
+
+		// Is user viewing their own profile?
+		$is_user_home = bbp_is_user_home();
+
+		// User topics created
+		if ( bbp_is_single_user_topics() ) {
+			if ( true === $is_user_home ) {
+				$new_title['text'] = esc_attr__( 'Your Topics', 'bbpress' );
+			} else {
+				$new_title['text'] = get_userdata( bbp_get_user_id() )->display_name;
+				/* translators: user's display name */
+				$new_title['format'] = esc_attr__( "%s's Topics", 'bbpress' );
+			}
+
+		// User replies created
+		} elseif ( bbp_is_single_user_replies() ) {
+			if ( true === $is_user_home ) {
+				$new_title['text'] = esc_attr__( 'Your Replies', 'bbpress' );
+			} else {
+				$new_title['text'] = get_userdata( bbp_get_user_id() )->display_name;
+				/* translators: user's display name */
+				$new_title['format'] = esc_attr__( "%s's Replies", 'bbpress' );
+			}
+
+		// User favorites
+		} elseif ( bbp_is_favorites() ) {
+			if ( true === $is_user_home ) {
+				$new_title['text'] = esc_attr__( 'Your Favorites', 'bbpress' );
+			} else {
+				$new_title['text'] = get_userdata( bbp_get_user_id() )->display_name;
+				/* translators: user's display name */
+				$new_title['format'] = esc_attr__( "%s's Favorites", 'bbpress' );
+			}
+
+		// User subscriptions
+		} elseif ( bbp_is_subscriptions() ) {
+			if ( true === $is_user_home ) {
+				$new_title['text'] = esc_attr__( 'Your Subscriptions', 'bbpress' );
+			} else {
+				$new_title['text'] = get_userdata( bbp_get_user_id() )->display_name;
+				/* translators: user's display name */
+				$new_title['format'] = esc_attr__( "%s's Subscriptions", 'bbpress' );
+			}
+
+		// User "home"
+		} else {
+			if ( true === $is_user_home ) {
+				$new_title['text'] = esc_attr__( 'Your Profile', 'bbpress' );
+			} else {
+				$new_title['text'] = get_userdata( bbp_get_user_id() )->display_name;
+				/* translators: user's display name */
+				$new_title['format'] = esc_attr__( "%s's Profile", 'bbpress' );
+			}
+		}
+
+	// Profile edit page
+	} elseif ( bbp_is_single_user_edit() ) {
+
+		// Current user
+		if ( bbp_is_user_home_edit() ) {
+			$new_title['text']   = esc_attr__( 'Edit Your Profile', 'bbpress' );
+
+		// Other user
+		} else {
+			$new_title['text']   = get_userdata( bbp_get_user_id() )->display_name;
+			$new_title['format'] = esc_attr__( "Edit %s's Profile", 'bbpress' );
+		}
+
+	/** Views *****************************************************************/
+
+	// Views
+	} elseif ( bbp_is_single_view() ) {
+		$new_title['text']   = bbp_get_view_title();
+		$new_title['format'] = esc_attr__( 'View: %s', 'bbpress' );
+
+	/** Search ****************************************************************/
+
+	// Search
+	} elseif ( bbp_is_search() ) {
+		$new_title['text'] = bbp_get_search_title();
+	}
+
+	// This filter is deprecated. Use 'bbp_before_title_parse_args' instead.
+	$new_title = apply_filters( 'bbp_raw_title_array', $new_title );
+
+	// Set title array defaults
+	$new_title = bbp_parse_args( $new_title, array(
+		'text'   => $title,
+		'format' => '%s'
+	), 'title' );
+
+	// Get the formatted raw title
+	$new_title = sprintf( $new_title['format'], $new_title['text'] );
+
+	// Filter the raw title
+	$new_title = apply_filters( 'bbp_raw_title', $new_title, $sep, $seplocation );
+
+	// Compare new title with original title
+	if ( $new_title === $title ) {
+		return $title;
+	}
+
+	// Temporary separator, for accurate flipping, if necessary
+	$t_sep  = '%WP_TITILE_SEP%';
+	$prefix = '';
+
+	if ( ! empty( $new_title ) ) {
+		$prefix = " $sep ";
+	}
+
+	// sep on right, so reverse the order
+	if ( 'right' === $seplocation ) {
+		$new_title_array = array_reverse( explode( $t_sep, $new_title ) );
+		$new_title       = implode( " $sep ", $new_title_array ) . $prefix;
+
+	// sep on left, do not reverse
+	} else {
+		$new_title_array = explode( $t_sep, $new_title );
+		$new_title       = $prefix . implode( " $sep ", $new_title_array );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_title', $new_title, $sep, $seplocation );
+}
Index: /tags/2.6.6/src/includes/common/widgets.php
===================================================================
--- /tags/2.6.6/src/includes/common/widgets.php	(revision 7162)
+++ /tags/2.6.6/src/includes/common/widgets.php	(revision 7162)
@@ -0,0 +1,1241 @@
+<?php
+
+/**
+ * bbPress Widgets
+ *
+ * Contains the forum list, topic list, reply list and login form widgets.
+ *
+ * @package bbPress
+ * @subpackage Widgets
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * bbPress Login Widget
+ *
+ * Adds a widget which displays the login form
+ *
+ * @since 2.0.0 bbPress (r2827)
+ */
+class BBP_Login_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Login Widget
+	 *
+	 * Registers the login widget
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_login_widget_options', array(
+			'classname'                   => 'bbp_widget_login',
+			'description'                 => esc_html__( 'A simple login form with optional links to sign-up and lost password pages.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Login Widget', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.0.0 bbPress (r3389)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Login_Widget' );
+	}
+
+	/**
+	 * Displays the output, the login form
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 *
+	 * @param array $args Arguments
+	 * @param array $instance Instance
+	 */
+	public function widget( $args = array(), $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title', $settings['title'], $instance, $this->id_base );
+
+		// bbPress filters
+		$settings['title']    = apply_filters( 'bbp_login_widget_title',    $settings['title'],    $instance, $this->id_base );
+		$settings['register'] = apply_filters( 'bbp_login_widget_register', $settings['register'], $instance, $this->id_base );
+		$settings['lostpass'] = apply_filters( 'bbp_login_widget_lostpass', $settings['lostpass'], $instance, $this->id_base );
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		}
+
+		if ( ! is_user_logged_in() ) : ?>
+
+			<form method="post" action="<?php bbp_wp_login_action( array( 'context' => 'login_post' ) ); ?>" class="bbp-login-form">
+				<fieldset class="bbp-form">
+					<legend><?php esc_html_e( 'Log In', 'bbpress' ); ?></legend>
+
+					<div class="bbp-username">
+						<label for="user_login"><?php esc_html_e( 'Username', 'bbpress' ); ?>: </label>
+						<input type="text" name="log" value="<?php bbp_sanitize_val( 'user_login', 'text' ); ?>" size="20" maxlength="100" id="user_login" autocomplete="off" />
+					</div>
+
+					<div class="bbp-password">
+						<label for="user_pass"><?php esc_html_e( 'Password', 'bbpress' ); ?>: </label>
+						<input type="password" name="pwd" value="<?php bbp_sanitize_val( 'user_pass', 'password' ); ?>" size="20" id="user_pass" autocomplete="off" />
+					</div>
+
+					<div class="bbp-remember-me">
+						<input type="checkbox" name="rememberme" value="forever" <?php checked( bbp_get_sanitize_val( 'rememberme', 'checkbox' ) ); ?> id="rememberme" />
+						<label for="rememberme"><?php esc_html_e( 'Keep me signed in', 'bbpress' ); ?></label>
+					</div>
+
+					<?php do_action( 'login_form' ); ?>
+
+					<div class="bbp-submit-wrapper">
+
+						<button type="submit" name="user-submit" id="user-submit" class="button submit user-submit"><?php esc_html_e( 'Log In', 'bbpress' ); ?></button>
+
+						<?php bbp_user_login_fields(); ?>
+
+					</div>
+
+					<?php if ( ! empty( $settings['register'] ) || ! empty( $settings['lostpass'] ) ) : ?>
+
+						<div class="bbp-login-links">
+
+							<?php if ( ! empty( $settings['register'] ) ) : ?>
+
+								<a href="<?php echo esc_url( $settings['register'] ); ?>" title="<?php esc_attr_e( 'Register', 'bbpress' ); ?>" class="bbp-register-link"><?php esc_html_e( 'Register', 'bbpress' ); ?></a>
+
+							<?php endif; ?>
+
+							<?php if ( ! empty( $settings['lostpass'] ) ) : ?>
+
+								<a href="<?php echo esc_url( $settings['lostpass'] ); ?>" title="<?php esc_attr_e( 'Lost Password', 'bbpress' ); ?>" class="bbp-lostpass-link"><?php esc_html_e( 'Lost Password', 'bbpress' ); ?></a>
+
+							<?php endif; ?>
+
+						</div>
+
+					<?php endif; ?>
+
+				</fieldset>
+			</form>
+
+		<?php else : ?>
+
+			<div class="bbp-logged-in">
+				<a href="<?php bbp_user_profile_url( bbp_get_current_user_id() ); ?>" class="submit user-submit"><?php echo get_avatar( bbp_get_current_user_id(), '40' ); ?></a>
+				<h4><?php bbp_user_profile_link( bbp_get_current_user_id() ); ?></h4>
+
+				<?php bbp_logout_link(); ?>
+			</div>
+
+		<?php endif;
+
+		echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the login widget options
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance             = $old_instance;
+		$instance['title']    = strip_tags( $new_instance['title'] );
+		$instance['register'] = esc_url_raw( $new_instance['register'] );
+		$instance['lostpass'] = esc_url_raw( $new_instance['lostpass'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the login widget options form
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'bbpress' ); ?>
+			<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $settings['title'] ); ?>" /></label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'register' ); ?>"><?php esc_html_e( 'Register URI:', 'bbpress' ); ?>
+			<input class="widefat" id="<?php echo $this->get_field_id( 'register' ); ?>" name="<?php echo $this->get_field_name( 'register' ); ?>" type="text" value="<?php echo esc_url( $settings['register'] ); ?>" /></label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'lostpass' ); ?>"><?php esc_html_e( 'Lost Password URI:', 'bbpress' ); ?>
+			<input class="widefat" id="<?php echo $this->get_field_id( 'lostpass' ); ?>" name="<?php echo $this->get_field_name( 'lostpass' ); ?>" type="text" value="<?php echo esc_url( $settings['lostpass'] ); ?>" /></label>
+		</p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title'    => '',
+			'register' => '',
+			'lostpass' => ''
+		), 'login_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Views Widget
+ *
+ * Adds a widget which displays the view list
+ *
+ * @since 2.0.0 bbPress (r3020)
+ */
+class BBP_Views_Widget extends WP_Widget {
+
+	/**
+	 * bbPress View Widget
+	 *
+	 * Registers the view widget
+	 *
+	 * @since 2.0.0 bbPress (r3020)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_views_widget_options', array(
+			'classname'                   => 'widget_display_views',
+			'description'                 => esc_html__( 'A list of registered optional topic views.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Topic Views List', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.0.0 bbPress (r3389)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Views_Widget' );
+	}
+
+	/**
+	 * Displays the output, the view list
+	 *
+	 * @since 2.0.0 bbPress (r3020)
+	 *
+	 * @param array $args Arguments
+	 * @param array $instance Instance
+	 */
+	public function widget( $args = array(), $instance = array() ) {
+
+		// Only output widget contents if views exist
+		if ( ! bbp_get_views() ) {
+			return;
+		}
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',          $settings['title'], $instance, $this->id_base );
+
+		// bbPress filter
+		$settings['title'] = apply_filters( 'bbp_view_widget_title', $settings['title'], $instance, $this->id_base );
+
+		// Start an output buffer
+		ob_start();
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		} ?>
+
+		<ul class="bbp-views-widget">
+
+			<?php foreach ( array_keys( bbp_get_views() ) as $view ) : ?>
+
+				<li><a class="bbp-view-title" href="<?php bbp_view_url( $view ); ?>"><?php bbp_view_title( $view ); ?></a></li>
+
+			<?php endforeach; ?>
+
+		</ul>
+
+		<?php echo $args['after_widget'];
+
+		// Output the current buffer
+		echo ob_get_clean();
+	}
+
+	/**
+	 * Update the view widget options
+	 *
+	 * @since 2.0.0 bbPress (r3020)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance = array(), $old_instance = array() ) {
+		$instance          = $old_instance;
+		$instance['title'] = strip_tags( $new_instance['title'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the view widget options form
+	 *
+	 * @since 2.0.0 bbPress (r3020)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $settings['title'] ); ?>" />
+			</label>
+		</p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title' => ''
+		), 'view_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Search Widget
+ *
+ * Adds a widget which displays the forum search form
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+class BBP_Search_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Search Widget
+	 *
+	 * Registers the search widget
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_search_widget_options', array(
+			'classname'                   => 'widget_display_search',
+			'description'                 => esc_html__( 'The bbPress forum search form.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Forum Search Form', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Search_Widget' );
+	}
+
+	/**
+	 * Displays the output, the search form
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 */
+	public function widget( $args, $instance ) {
+
+		// Bail if search is disabled
+		if ( ! bbp_allow_search() ) {
+			return;
+		}
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',            $settings['title'], $instance, $this->id_base );
+
+		// bbPress filter
+		$settings['title'] = apply_filters( 'bbp_search_widget_title', $settings['title'], $instance, $this->id_base );
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		}
+
+		bbp_get_template_part( 'form', 'search' );
+
+		echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the widget options
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance          = $old_instance;
+		$instance['title'] = strip_tags( $new_instance['title'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the search widget options form
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $settings['title'] ); ?>" />
+			</label>
+		</p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title' => esc_html__( 'Search Forums', 'bbpress' )
+		), 'search_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Forum Widget
+ *
+ * Adds a widget which displays the forum list
+ *
+ * @since 2.0.0 bbPress (r2653)
+ */
+class BBP_Forums_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Forum Widget
+	 *
+	 * Registers the forum widget
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_forums_widget_options', array(
+			'classname'                   => 'widget_display_forums',
+			'description'                 => esc_html__( 'A list of forums with an option to set the parent.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Forums List', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.0.0 bbPress (r3389)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Forums_Widget' );
+	}
+
+	/**
+	 * Displays the output, the forum list
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $args Arguments
+	 * @param array $instance Instance
+	 */
+	public function widget( $args, $instance ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',           $settings['title'], $instance, $this->id_base );
+
+		// bbPress filter
+		$settings['title'] = apply_filters( 'bbp_forum_widget_title', $settings['title'], $instance, $this->id_base );
+
+		// Note: private and hidden forums will be excluded via the
+		// bbp_pre_get_posts_normalize_forum_visibility action and function.
+		$widget_query = new WP_Query( array(
+
+			// What and how
+			'post_type'      => bbp_get_forum_post_type(),
+			'post_status'    => bbp_get_public_status_id(),
+			'post_parent'    => $settings['parent_forum'],
+			'posts_per_page' => (int) get_option( '_bbp_forums_per_page', 50 ),
+
+			// Order
+			'orderby' => 'menu_order title',
+			'order'   => 'ASC',
+
+			// Performance
+			'ignore_sticky_posts'    => true,
+			'no_found_rows'          => true,
+			'update_post_term_cache' => false,
+			'update_post_meta_cache' => false
+		) );
+
+		// Bail if no posts
+		if ( ! $widget_query->have_posts() ) {
+			return;
+		}
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		} ?>
+
+		<ul class="bbp-forums-widget">
+
+			<?php while ( $widget_query->have_posts() ) : $widget_query->the_post(); ?>
+
+				<li <?php echo ( bbp_get_forum_id() === $widget_query->post->ID ? ' class="bbp-forum-widget-current-forum"' : '' ); ?>>
+					<a class="bbp-forum-title" href="<?php bbp_forum_permalink( $widget_query->post->ID ); ?>">
+						<?php bbp_forum_title( $widget_query->post->ID ); ?>
+					</a>
+				</li>
+
+			<?php endwhile; ?>
+
+		</ul>
+
+		<?php echo $args['after_widget'];
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+
+	/**
+	 * Update the forum widget options
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance                 = $old_instance;
+		$instance['title']        = strip_tags( $new_instance['title'] );
+		$instance['parent_forum'] = sanitize_text_field( $new_instance['parent_forum'] );
+
+		// Force to any
+		if ( ! empty( $instance['parent_forum'] ) && ! is_numeric( $instance['parent_forum'] ) ) {
+			$instance['parent_forum'] = 'any';
+		}
+
+		return $instance;
+	}
+
+	/**
+	 * Output the forum widget options form
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $settings['title'] ); ?>" />
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'parent_forum' ); ?>"><?php esc_html_e( 'Parent Forum ID:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'parent_forum' ); ?>" name="<?php echo $this->get_field_name( 'parent_forum' ); ?>" type="text" value="<?php echo esc_attr( $settings['parent_forum'] ); ?>" />
+			</label>
+
+			<br />
+
+			<small><?php esc_html_e( '"0" to show only root - "any" to show all', 'bbpress' ); ?></small>
+		</p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title'        => esc_html__( 'Forums', 'bbpress' ),
+			'parent_forum' => 0
+		), 'forum_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Topic Widget
+ *
+ * Adds a widget which displays the topic list
+ *
+ * @since 2.0.0 bbPress (r2653)
+ */
+class BBP_Topics_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Topic Widget
+	 *
+	 * Registers the topic widget
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_topics_widget_options', array(
+			'classname'                   => 'widget_display_topics',
+			'description'                 => esc_html__( 'A list of recent topics, sorted by: newness, popularity, or recent replies.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Recent Topics', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.0.0 bbPress (r3389)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Topics_Widget' );
+	}
+
+	/**
+	 * Displays the output, the topic list
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $args
+	 * @param array $instance
+	 */
+	public function widget( $args = array(), $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',           $settings['title'], $instance, $this->id_base );
+
+		// bbPress filter
+		$settings['title'] = apply_filters( 'bbp_topic_widget_title', $settings['title'], $instance, $this->id_base );
+
+		// How do we want to order our results?
+		switch ( $settings['order_by'] ) {
+
+			// Order by most recent replies
+			case 'freshness' :
+				$topics_query = array(
+
+					// What and how
+					'post_type'      => bbp_get_topic_post_type(),
+					'post_status'    => bbp_get_public_topic_statuses(),
+					'post_parent'    => $settings['parent_forum'],
+					'posts_per_page' => (int) $settings['max_shown'],
+					'meta_query'     => array( array(
+						'key'  => '_bbp_last_active_time',
+						'type' => 'DATETIME'
+					) ),
+
+					// Ordering
+					'orderby' => 'meta_value',
+					'order'   => 'DESC',
+
+					// Performance
+					'ignore_sticky_posts'    => true,
+					'no_found_rows'          => true,
+					'update_post_term_cache' => false,
+					'update_post_meta_cache' => false
+				);
+				break;
+
+			// Order by total number of replies
+			case 'popular' :
+				$topics_query = array(
+
+					// What and how
+					'post_type'      => bbp_get_topic_post_type(),
+					'post_status'    => bbp_get_public_topic_statuses(),
+					'post_parent'    => $settings['parent_forum'],
+					'posts_per_page' => (int) $settings['max_shown'],
+					'meta_query'     => array( array(
+						'key'  => '_bbp_reply_count',
+						'type' => 'NUMERIC'
+					) ),
+
+					// Ordering
+					'orderby' => 'meta_value_num',
+					'order'   => 'DESC',
+
+					// Performance
+					'ignore_sticky_posts'    => true,
+					'no_found_rows'          => true,
+					'update_post_term_cache' => false,
+					'update_post_meta_cache' => false
+				);
+				break;
+
+			// Order by which topic was created most recently
+			case 'newness' :
+			default :
+				$topics_query = array(
+
+					// What and how
+					'post_type'      => bbp_get_topic_post_type(),
+					'post_status'    => bbp_get_public_topic_statuses(),
+					'post_parent'    => $settings['parent_forum'],
+					'posts_per_page' => (int) $settings['max_shown'],
+
+					// Ordering
+					'orderby' => 'date',
+					'order'   => 'DESC',
+
+					// Performance
+					'ignore_sticky_posts'    => true,
+					'no_found_rows'          => true,
+					'update_post_term_cache' => false,
+					'update_post_meta_cache' => false
+				);
+				break;
+		}
+
+		// Note: private and hidden forums will be excluded via the
+		// bbp_pre_get_posts_normalize_forum_visibility action and function.
+		$widget_query = new WP_Query( $topics_query );
+
+		// Bail if no topics are found
+		if ( ! $widget_query->have_posts() ) {
+			return;
+		}
+
+		// Start an output buffer
+		ob_start();
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		} ?>
+
+		<ul class="bbp-topics-widget <?php echo esc_attr( $settings['order_by'] ); ?>">
+
+			<?php while ( $widget_query->have_posts() ) :
+
+				$widget_query->the_post();
+				$topic_id    = bbp_get_topic_id( $widget_query->post->ID );
+				$author_link = '';
+
+				// Maybe get the topic author
+				if ( ! empty( $settings['show_user'] ) ) :
+					$author_link = bbp_get_topic_author_link( array( 'post_id' => $topic_id, 'type' => 'both', 'size' => 14 ) );
+				endif; ?>
+
+				<li>
+					<a class="bbp-forum-title" href="<?php bbp_topic_permalink( $topic_id ); ?>"><?php bbp_topic_title( $topic_id ); ?></a>
+
+					<?php if ( ! empty( $author_link ) ) : ?>
+
+						<?php printf( esc_html_x( 'by %1$s', 'widgets', 'bbpress' ), '<span class="topic-author">' . $author_link . '</span>' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( ! empty( $settings['show_date'] ) ) : ?>
+
+						<div><?php bbp_topic_last_active_time( $topic_id ); ?></div>
+
+					<?php endif; ?>
+
+				</li>
+
+			<?php endwhile; ?>
+
+		</ul>
+
+		<?php echo $args['after_widget'];
+
+		// Reset the $post global
+		wp_reset_postdata();
+
+		// Output the current buffer
+		echo ob_get_clean();
+	}
+
+	/**
+	 * Update the topic widget options
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance = array(), $old_instance = array() ) {
+		$instance                 = $old_instance;
+		$instance['title']        = strip_tags( $new_instance['title'] );
+		$instance['order_by']     = strip_tags( $new_instance['order_by'] );
+		$instance['parent_forum'] = sanitize_text_field( $new_instance['parent_forum'] );
+		$instance['max_shown']    = (int) $new_instance['max_shown'];
+
+		// Date
+		$instance['show_date'] = isset( $new_instance['show_date'] )
+			? (bool) $new_instance['show_date']
+			: false;
+
+		// Author
+		$instance['show_user'] = isset( $new_instance['show_user'] )
+			? (bool) $new_instance['show_user']
+			: false;
+
+		// Force to any
+		if ( ! empty( $instance['parent_forum'] ) && ! is_numeric( $instance['parent_forum'] ) ) {
+			$instance['parent_forum'] = 'any';
+		}
+
+		return $instance;
+	}
+
+	/**
+	 * Output the topic widget options form
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p><label for="<?php echo $this->get_field_id( 'title'     ); ?>"><?php esc_html_e( 'Title:',                  'bbpress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'title'     ); ?>" name="<?php echo $this->get_field_name( 'title'     ); ?>" type="text" value="<?php echo esc_attr( $settings['title']     ); ?>" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'max_shown' ); ?>"><?php esc_html_e( 'Maximum topics to show:', 'bbpress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'max_shown' ); ?>" name="<?php echo $this->get_field_name( 'max_shown' ); ?>" type="text" value="<?php echo esc_attr( $settings['max_shown'] ); ?>" /></label></p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'parent_forum' ); ?>"><?php esc_html_e( 'Parent Forum ID:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'parent_forum' ); ?>" name="<?php echo $this->get_field_name( 'parent_forum' ); ?>" type="text" value="<?php echo esc_attr( $settings['parent_forum'] ); ?>" />
+			</label>
+
+			<br />
+
+			<small><?php esc_html_e( '"0" to show only root - "any" to show all', 'bbpress' ); ?></small>
+		</p>
+
+		<p><label for="<?php echo $this->get_field_id( 'show_date' ); ?>"><?php esc_html_e( 'Show post date:',    'bbpress' ); ?> <input type="checkbox" id="<?php echo $this->get_field_id( 'show_date' ); ?>" name="<?php echo $this->get_field_name( 'show_date' ); ?>" <?php checked( true, $settings['show_date'] ); ?> value="1" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'show_user' ); ?>"><?php esc_html_e( 'Show topic author:', 'bbpress' ); ?> <input type="checkbox" id="<?php echo $this->get_field_id( 'show_user' ); ?>" name="<?php echo $this->get_field_name( 'show_user' ); ?>" <?php checked( true, $settings['show_user'] ); ?> value="1" /></label></p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'order_by' ); ?>"><?php esc_html_e( 'Order By:', 'bbpress' ); ?></label>
+			<select name="<?php echo $this->get_field_name( 'order_by' ); ?>" id="<?php echo $this->get_field_id( 'order_by' ); ?>">
+				<option <?php selected( $settings['order_by'], 'newness'   ); ?> value="newness"><?php esc_html_e( 'Newest Topics',                'bbpress' ); ?></option>
+				<option <?php selected( $settings['order_by'], 'popular'   ); ?> value="popular"><?php esc_html_e( 'Popular Topics',               'bbpress' ); ?></option>
+				<option <?php selected( $settings['order_by'], 'freshness' ); ?> value="freshness"><?php esc_html_e( 'Topics With Recent Replies', 'bbpress' ); ?></option>
+			</select>
+		</p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title'        => esc_html__( 'Recent Topics', 'bbpress' ),
+			'max_shown'    => 5,
+			'show_date'    => false,
+			'show_user'    => false,
+			'parent_forum' => 'any',
+			'order_by'     => false
+		), 'topic_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Statistics Widget
+ *
+ * Adds a widget which displays the forum statistics
+ *
+ * @since 2.3.0 bbPress (r4509)
+ */
+class BBP_Stats_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Statistics Widget
+	 *
+	 * Registers the statistics widget
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_stats_widget_options', array(
+			'classname'                   => 'widget_display_stats',
+			'description'                 => esc_html__( 'Some statistics from your forum.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Statistics', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Stats_Widget' );
+	}
+
+	/**
+	 * Displays the output, the statistics
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 *
+	 * @param array $args     Arguments
+	 * @param array $instance Instance
+	 */
+	public function widget( $args = array(), $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',           $settings['title'], $instance, $this->id_base );
+
+		// bbPress widget title filter
+		$settings['title'] = apply_filters( 'bbp_stats_widget_title', $settings['title'], $instance, $this->id_base );
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		}
+
+		bbp_get_template_part( 'content', 'statistics' );
+
+		echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the statistics widget options
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 *
+	 * @return array
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance          = $old_instance;
+		$instance['title'] = strip_tags( $new_instance['title'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the statistics widget options form
+	 *
+	 * @since 2.3.0 bbPress (r4509)
+	 *
+	 * @param $instance
+	 *
+	 * @return string|void
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php esc_html_e( 'Title:', 'bbpress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $settings['title'] ); ?>"/>
+			</label>
+		</p>
+
+	<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title' => esc_html__( 'Forum Statistics', 'bbpress' )
+		), 'stats_widget_settings' );
+	}
+}
+
+/**
+ * bbPress Replies Widget
+ *
+ * Adds a widget which displays the replies list
+ *
+ * @since 2.0.0 bbPress (r2653)
+ */
+class BBP_Replies_Widget extends WP_Widget {
+
+	/**
+	 * bbPress Replies Widget
+	 *
+	 * Registers the replies widget
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 */
+	public function __construct() {
+		$widget_ops = apply_filters( 'bbp_replies_widget_options', array(
+			'classname'                   => 'widget_display_replies',
+			'description'                 => esc_html__( 'A list of the most recent replies.', 'bbpress' ),
+			'customize_selective_refresh' => true
+		) );
+
+		parent::__construct( false, esc_html__( '(bbPress) Recent Replies', 'bbpress' ), $widget_ops );
+	}
+
+	/**
+	 * Register the widget
+	 *
+	 * @since 2.0.0 bbPress (r3389)
+	 */
+	public static function register_widget() {
+		register_widget( 'BBP_Replies_Widget' );
+	}
+
+	/**
+	 * Displays the output, the replies list
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $args
+	 * @param array $instance
+	 */
+	public function widget( $args, $instance ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance );
+
+		// Typical WordPress filter
+		$settings['title'] = apply_filters( 'widget_title',             $settings['title'], $instance, $this->id_base );
+
+		// bbPress filter
+		$settings['title'] = apply_filters( 'bbp_replies_widget_title', $settings['title'], $instance, $this->id_base );
+
+		// Note: private and hidden forums will be excluded via the
+		// bbp_pre_get_posts_normalize_forum_visibility action and function.
+		$widget_query = new WP_Query( array(
+
+			// What and when
+			'post_type'      => bbp_get_reply_post_type(),
+			'post_status'    => bbp_get_public_reply_statuses(),
+			'posts_per_page' => (int) $settings['max_shown'],
+
+			// Performance
+			'ignore_sticky_posts'    => true,
+			'no_found_rows'          => true,
+			'update_post_term_cache' => false,
+			'update_post_meta_cache' => false
+		) );
+
+		// Bail if no replies
+		if ( ! $widget_query->have_posts() ) {
+			return;
+		}
+
+		// Start an output buffer
+		ob_start();
+
+		echo $args['before_widget'];
+
+		if ( ! empty( $settings['title'] ) ) {
+			echo $args['before_title'] . $settings['title'] . $args['after_title'];
+		} ?>
+
+		<ul class="bbp-replies-widget">
+
+			<?php while ( $widget_query->have_posts() ) : $widget_query->the_post(); ?>
+
+				<li>
+
+					<?php
+
+					// Verify the reply ID
+					$reply_id   = bbp_get_reply_id( $widget_query->post->ID );
+					$reply_link = '<a class="bbp-reply-topic-title" href="' . esc_url( bbp_get_reply_url( $reply_id ) ) . '" title="' . esc_attr( bbp_get_reply_excerpt( $reply_id, 50 ) ) . '">' . esc_html( bbp_get_reply_topic_title( $reply_id ) ) . '</a>';
+					$time       = get_the_time( 'U', $reply_id );
+					$show_date  = '<time datetime="' . gmdate( 'Y-m-d H:i:s', $time ) . '">' . esc_html( bbp_get_time_since( $time ) ) . '</time>';
+
+					// Only query user if showing them
+					if ( ! empty( $settings['show_user'] ) ) :
+						$author_link = bbp_get_reply_author_link( array( 'post_id' => $reply_id, 'type' => 'both', 'size' => 14 ) );
+					else :
+						$author_link = false;
+					endif;
+
+					// Reply author, link, and timestamp
+					if ( ! empty( $settings['show_date'] ) && ! empty( $author_link ) ) :
+
+						// translators: 1: reply author, 2: reply link, 3: reply timestamp
+						printf( esc_html_x( '%1$s on %2$s %3$s', 'widgets', 'bbpress' ), $author_link, $reply_link, $show_date );
+
+					// Reply link and timestamp
+					elseif ( ! empty( $settings['show_date'] ) ) :
+						echo $reply_link . ' ' . $show_date;
+
+					// Reply author and title
+					elseif ( ! empty( $author_link ) ) :
+
+						// translators: 1: reply author, 2: reply link
+						printf( esc_html_x( '%1$s on %2$s', 'widgets', 'bbpress' ), $author_link, $reply_link );
+
+					// Only the reply title
+					else :
+						echo $reply_link;
+					endif;
+
+					?>
+
+				</li>
+
+			<?php endwhile; ?>
+
+		</ul>
+
+		<?php echo $args['after_widget'];
+
+		// Reset the $post global
+		wp_reset_postdata();
+
+		// Output the current buffer
+		echo ob_get_clean();
+	}
+
+	/**
+	 * Update the reply widget options
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param array $new_instance The new instance options
+	 * @param array $old_instance The old instance options
+	 */
+	public function update( $new_instance = array(), $old_instance = array() ) {
+		$instance              = $old_instance;
+		$instance['title']     = strip_tags( $new_instance['title'] );
+		$instance['max_shown'] = (int) $new_instance['max_shown'];
+
+		// Date
+		$instance['show_date'] = isset( $new_instance['show_date'] )
+			? (bool) $new_instance['show_date']
+			: false;
+
+		// Author
+		$instance['show_user'] = isset( $new_instance['show_user'] )
+			? (bool) $new_instance['show_user']
+			: false;
+
+		return $instance;
+	}
+
+	/**
+	 * Output the reply widget options form
+	 *
+	 * @since 2.0.0 bbPress (r2653)
+	 *
+	 * @param $instance Instance
+	 */
+	public function form( $instance = array() ) {
+
+		// Get widget settings
+		$settings = $this->parse_settings( $instance ); ?>
+
+		<p><label for="<?php echo $this->get_field_id( 'title'     ); ?>"><?php esc_html_e( 'Title:',                   'bbpress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'title'     ); ?>" name="<?php echo $this->get_field_name( 'title'     ); ?>" type="text" value="<?php echo esc_attr( $settings['title']     ); ?>" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'max_shown' ); ?>"><?php esc_html_e( 'Maximum replies to show:', 'bbpress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'max_shown' ); ?>" name="<?php echo $this->get_field_name( 'max_shown' ); ?>" type="text" value="<?php echo esc_attr( $settings['max_shown'] ); ?>" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'show_date' ); ?>"><?php esc_html_e( 'Show post date:',          'bbpress' ); ?> <input type="checkbox" id="<?php echo $this->get_field_id( 'show_date' ); ?>" name="<?php echo $this->get_field_name( 'show_date' ); ?>" <?php checked( true, $settings['show_date'] ); ?> value="1" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'show_user' ); ?>"><?php esc_html_e( 'Show reply author:',       'bbpress' ); ?> <input type="checkbox" id="<?php echo $this->get_field_id( 'show_user' ); ?>" name="<?php echo $this->get_field_name( 'show_user' ); ?>" <?php checked( true, $settings['show_user'] ); ?> value="1" /></label></p>
+
+		<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0 bbPress (r4802)
+	 *
+	 * @param $instance Instance
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bbp_parse_args( $instance, array(
+			'title'     => esc_html__( 'Recent Replies', 'bbpress' ),
+			'max_shown' => 5,
+			'show_date' => false,
+			'show_user' => false
+		), 'replies_widget_settings' );
+	}
+}
Index: /tags/2.6.6/src/includes/core/abstraction.php
===================================================================
--- /tags/2.6.6/src/includes/core/abstraction.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/abstraction.php	(revision 7162)
@@ -0,0 +1,498 @@
+<?php
+
+/**
+ * bbPress Abstractions
+ *
+ * This file contains functions for abstracting WordPress core functionality
+ * into convenient wrappers so they can be used more reliably.
+ *
+ * Many of the functions in this file are considered superfluous by
+ * WordPress coding standards, but they're handy for plugins of plugins to use.
+ *
+ * @package bbPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Setup Admin
+ *
+ * This exists outside of "/includes/admin/" because the converter may need to
+ * be setup to convert the passwords of users that were migrated from another
+ * forum platform.
+ *
+ * @since 2.6.0 bbPress (r2596)
+ */
+function bbp_setup_admin() {
+	$bbp = bbpress();
+
+	// Skip if already setup
+	if ( empty( $bbp->admin ) ) {
+
+		// Require the admin class
+		require_once $bbp->includes_dir . 'admin/classes/class-bbp-admin.php';
+
+		// Setup
+		$bbp->admin = class_exists( 'BBP_Admin' )
+			? new BBP_Admin()
+			: new stdClass();
+	}
+
+	// Return the admin object
+	return $bbp->admin;
+}
+
+/**
+ * Setup Converter
+ *
+ * This exists outside of "/includes/admin/" because the converter may need to
+ * be setup to convert the passwords of users that were migrated from another
+ * forum platform.
+ *
+ * @since 2.6.0 bbPress (r2596)
+ */
+function bbp_setup_converter() {
+	$bbp_admin = bbp_setup_admin();
+
+	// Skip if already setup
+	if ( empty( $bbp_admin->converter ) ) {
+
+		// Require the converter files
+		require_once $bbp_admin->admin_dir . 'tools/converter.php';
+		require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter.php';
+		require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter-db.php';
+		require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter-base.php';
+
+		// Setup
+		$bbp_admin->converter = class_exists( 'BBP_Converter' )
+			? new BBP_Converter()
+			: new stdClass();
+	}
+
+	// Return the converter
+	return $bbp_admin->converter;
+}
+
+/** Globals *******************************************************************/
+
+/**
+ * Lookup and return a global variable
+ *
+ * @since 2.5.8 bbPress (r5814)
+ *
+ * @param  string  $name     Name of global variable
+ * @param  string  $type     Type of variable to check with `is_a()`
+ * @param  mixed   $default  Default value to return if no global found
+ *
+ * @return mixed   Verified object if valid, Default or null if invalid
+ */
+function bbp_get_global_object( $name = '', $type = '', $default = null ) {
+
+	// If no name passed
+	if ( empty( $name ) ) {
+		$retval = $default;
+
+	// If no global exists
+	} elseif ( ! isset( $GLOBALS[ $name ] ) ) {
+		$retval = $default;
+
+	// If not the correct type of global
+	} elseif ( ! empty( $type ) && ! is_a( $GLOBALS[ $name ], $type ) ) {
+		$retval = $default;
+
+	// Global variable exists
+	} else {
+		$retval = $GLOBALS[ $name ];
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_global_object', $retval, $name, $type, $default );
+}
+
+/**
+ * Get the `$wp_query` global without needing to declare it everywhere
+ *
+ * @since 2.6.0 bbPress (r6582)
+ *
+ * @return WP_Roles
+ */
+function bbp_get_wp_query() {
+	return bbp_get_global_object( 'wp_query', 'WP_Query' );
+}
+
+/**
+ * Get the `$wp_roles` global without needing to declare it everywhere
+ *
+ * @since 2.2.0 bbPress (r4293)
+ *
+ * @return WP_Roles
+ */
+function bbp_get_wp_roles() {
+	return bbp_get_global_object( 'wp_roles', 'WP_Roles' );
+}
+
+/**
+ * Return the database class being used to interface with the environment.
+ *
+ * This function is abstracted to avoid global touches to the primary database
+ * class. bbPress supports WordPress's `$wpdb` global by default, and can be
+ * filtered to support other configurations if needed.
+ *
+ * @since 2.5.8 bbPress (r5814)
+ *
+ * @return object
+ */
+function bbp_db() {
+	return bbp_get_global_object( 'wpdb', 'WPDB' );
+}
+
+/** Pagination ****************************************************************/
+
+/**
+ * Return the rewrite rules class being used to interact with URLs.
+ *
+ * This function is abstracted to avoid global touches to the primary rewrite
+ * rules class. bbPress supports WordPress's `$wp_rewrite` by default, but can
+ * be filtered to support other configurations if needed.
+ *
+ * @since 2.5.8 bbPress (r5814)
+ *
+ * @return object
+ */
+function bbp_rewrite() {
+	return bbp_get_global_object( 'wp_rewrite', 'WP_Rewrite', (object) array(
+		'root'            => '',
+		'pagination_base' => 'page',
+	) );
+}
+
+/**
+ * Get the root URL
+ *
+ * @since 2.5.8 bbPress (r5814)
+ *
+ * @return string
+ */
+function bbp_get_root_url() {
+
+	// Default
+	$retval  = '';
+	$rewrite = bbp_rewrite();
+
+	// Use $wp_rewrite->root if available
+	if ( property_exists( $rewrite, 'root' ) ) {
+		$retval = $rewrite->root;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_root_url', $retval );
+}
+
+/**
+ * Get the slug used for paginated requests
+ *
+ * @since 2.4.0 bbPress (r4926)
+ *
+ * @return string
+ */
+function bbp_get_paged_slug() {
+
+	// Default
+	$retval  = 'page';
+	$rewrite = bbp_rewrite();
+
+	// Use $wp_rewrite->pagination_base if available
+	if ( property_exists( $rewrite, 'pagination_base' ) ) {
+		$retval = $rewrite->pagination_base;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_paged_slug', $retval );
+}
+
+/**
+ * Is the environment using pretty URLs?
+ *
+ * @since 2.5.8 bbPress (r5814)
+ *
+ * @global object $wp_rewrite The WP_Rewrite object
+ *
+ * @return bool
+ */
+function bbp_use_pretty_urls() {
+
+	// Default
+	$retval  = false;
+	$rewrite = bbp_rewrite();
+
+	// Use $wp_rewrite->using_permalinks() if available
+	if ( method_exists( $rewrite, 'using_permalinks' ) ) {
+		$retval = $rewrite->using_permalinks();
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_pretty_urls', $retval );
+}
+
+/**
+ * Remove the first-page from a pagination links result set, ensuring that it
+ * points to the canonical first page URL.
+ *
+ * This is a bit of an SEO hack, to guarantee that the first page in a loop will
+ * never have pagination appended to the end of it, regardless of what the other
+ * functions have decided for us.
+ *
+ * @since 2.6.0 bbPress (r6678)
+ *
+ * @param string $pagination_links The HTML links used for pagination
+ *
+ * @return string
+ */
+function bbp_make_first_page_canonical( $pagination_links = '' ) {
+
+	// Default value
+	$retval = $pagination_links;
+
+	// Remove first page from pagination
+	if ( ! empty( $pagination_links ) ) {
+		$retval = bbp_use_pretty_urls()
+			? str_replace( bbp_get_paged_slug() . '/1/', '', $pagination_links )
+			: preg_replace( '/&#038;paged=1(?=[^0-9])/m', '', $pagination_links );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_make_first_page_canonical', $retval, $pagination_links );
+}
+
+/**
+ * A convenient wrapper for common calls to paginate_links(), complete with
+ * support for parameters that aren't used internally by bbPress.
+ *
+ * @since 2.6.0 bbPress (r6679)
+ *
+ * @param array $args
+ *
+ * @return string
+ */
+function bbp_paginate_links( $args = array() ) {
+
+	// Maybe add view-all args
+	$add_args = empty( $args['add_args'] ) && bbp_get_view_all()
+		? array( 'view' => 'all' )
+		: false;
+
+	// Pagination settings with filter
+	$r = bbp_parse_args( $args, array(
+
+		// Used by callers
+		'base'      => '',
+		'total'     => 1,
+		'current'   => bbp_get_paged(),
+		'prev_next' => true,
+		'prev_text' => is_rtl() ? '&rarr;' : '&larr;',
+		'next_text' => is_rtl() ? '&larr;' : '&rarr;',
+		'mid_size'  => 1,
+		'end_size'  => 3,
+		'add_args'  => $add_args,
+
+		// Unused by callers
+		'show_all'           => false,
+		'type'               => 'plain',
+		'format'             => '',
+		'add_fragment'       => '',
+		'before_page_number' => '',
+		'after_page_number'  => ''
+	), 'paginate_links' );
+
+	// Return paginated links
+	return bbp_make_first_page_canonical( paginate_links( $r ) );
+}
+
+/**
+ * Parse the WordPress core version number
+ *
+ * @since 2.6.0 bbPress (r6051)
+ *
+ * @global string $wp_version
+ *
+ * @return string $wp_version
+ */
+function bbp_get_major_wp_version() {
+	global $wp_version;
+
+	return (float) $wp_version;
+}
+
+/** Multisite *****************************************************************/
+
+/**
+ * Is this a large bbPress installation?
+ *
+ * @since 2.6.0 bbPress (r6242)
+ *
+ * @return bool True if more than 10000 users, false not
+ */
+function bbp_is_large_install() {
+
+	// Multisite has a function specifically for this
+	$retval = function_exists( 'wp_is_large_network' )
+		? wp_is_large_network( 'users' )
+		: ( bbp_get_total_users() > 10000 );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_large_install', $retval );
+}
+
+/**
+ * Get the total number of users on the forums
+ *
+ * @since 2.0.0 bbPress (r2769)
+ *
+ * @return int Total number of users
+ */
+function bbp_get_total_users() {
+	$bbp_db = bbp_db();
+	$count  = $bbp_db->get_var( "SELECT COUNT(ID) as c FROM {$bbp_db->users} WHERE user_status = '0'" );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_total_users', (int) $count );
+}
+
+/**
+ * Switch to a site in a multisite installation.
+ *
+ * If not a multisite installation, no switching will occur.
+ *
+ * @since 2.6.0 bbPress (r6733)
+ *
+ * @param int $site_id
+ */
+function bbp_switch_to_site( $site_id = 0 ) {
+
+	// Switch to a specific site
+	if ( is_multisite() ) {
+		switch_to_blog( $site_id );
+	}
+}
+
+/**
+ * Switch back to the original site in a multisite installation.
+ *
+ * If not a multisite installation, no switching will occur.
+ *
+ * @since 2.6.0 bbPress (r6733)
+ */
+function bbp_restore_current_site() {
+
+	// Switch back to the original site
+	if ( is_multisite() ) {
+		restore_current_blog();
+	}
+}
+
+/** Interception **************************************************************/
+
+/**
+ * Generate a default intercept value.
+ *
+ * @since 2.6.0
+ *
+ * @staticvar mixed $rand Null by default, random string on first call
+ *
+ * @return string
+ */
+function bbp_default_intercept() {
+	static $rand = null;
+
+	// Generate a new random and unique string
+	if ( null === $rand ) {
+
+		// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
+		$algo = function_exists( 'hash' )
+			? 'sha256'
+			: 'sha1';
+
+		// Old WP installs may not have AUTH_SALT defined.
+		$salt = defined( 'AUTH_SALT' ) && AUTH_SALT
+			? AUTH_SALT
+			: (string) wp_rand();
+
+		// Create unique ID
+		$rand = hash_hmac( $algo, uniqid( $salt, true ), $salt );
+	}
+
+	// Return random string (from locally static variable)
+	return $rand;
+}
+
+/**
+ * Whether a value has been intercepted
+ *
+ * @since 2.6.0
+ *
+ * @param bool $value
+ */
+function bbp_is_intercepted( $value = '' ) {
+	return ( bbp_default_intercept() !== $value );
+}
+
+/**
+ * Allow interception of a method or function call.
+ *
+ * @since 2.6.0
+ *
+ * @param string $action Typically the name of the caller function
+ * @param array  $args   Typically the results of caller function func_get_args()
+ *
+ * @return mixed         Intercept results. Default bbp_default_intercept().
+ */
+function bbp_maybe_intercept( $action = '', $args = array() ) {
+
+	// Backwards compatibility juggle
+	$hook = ( false === strpos( $action, 'pre_' ) )
+		? "pre_{$action}"
+		: $action;
+
+	// Default value
+	$default = bbp_default_intercept();
+
+	// Parse args
+	$r = bbp_parse_args( (array) $args, array(), 'maybe_intercept' );
+
+	// Bail if no args
+	if ( empty( $r ) ) {
+		return $default;
+	}
+
+	// Filter
+	$args     = array_merge( array( $hook ), $r );
+	$filtered = call_user_func_array( 'apply_filters', $args );
+
+	// Return filtered value, or default if not intercepted
+	return ( $filtered === reset( $r ) )
+		? $default
+		: $filtered;
+}
+
+/** Date/Time *****************************************************************/
+
+/**
+ * Get an empty datetime value.
+ *
+ * @since 2.6.6 bbPress (r7094)
+ *
+ * @return string
+ */
+function bbp_get_empty_datetime() {
+
+	// Get the database version
+	$db_version = bbp_db()->db_version();
+
+	// Default return value
+	$retval = '0000-00-00 00:00:00';
+
+	// Filter & return
+	return (string) apply_filters( 'bbp_get_default_zero_date', $retval, $db_version );
+}
Index: /tags/2.6.6/src/includes/core/actions.php
===================================================================
--- /tags/2.6.6/src/includes/core/actions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/actions.php	(revision 7162)
@@ -0,0 +1,465 @@
+<?php
+
+/**
+ * bbPress Actions
+ *
+ * This file contains the actions that are used through-out bbPress. They are
+ * consolidated here to make searching for them easier, and to help developers
+ * understand at a glance the order in which things occur.
+ *
+ * There are a few common places that additional actions can currently be found
+ *
+ *  - bbPress: In {@link bbPress::setup_actions()} in bbpress.php
+ *  - Admin: More in {@link BBP_Admin::setup_actions()} in admin.php
+ *
+ * @package bbPress
+ * @subpackage Core
+ *
+ * @see /core/filters.php
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach bbPress to WordPress
+ *
+ * bbPress uses its own internal actions to help aid in third-party plugin
+ * development, and to limit the amount of potential future code changes when
+ * updates to WordPress core occur.
+ *
+ * These actions exist to create the concept of 'plugin dependencies'. They
+ * provide a safe way for plugins to execute code *only* when bbPress is
+ * installed and activated, without needing to do complicated guesswork.
+ *
+ * For more information on how this works, see the 'Plugin Dependency' section
+ * near the bottom of this file.
+ *
+ *           v--WordPress Actions        v--bbPress Sub-actions
+ */
+add_action( 'plugins_loaded',           'bbp_loaded',                 10    );
+add_action( 'init',                     'bbp_init',                   0     ); // Early for bbp_register
+add_action( 'parse_query',              'bbp_parse_query',            2     ); // Early for overrides
+add_action( 'generate_rewrite_rules',   'bbp_generate_rewrite_rules', 10    );
+add_action( 'after_setup_theme',        'bbp_after_setup_theme',      10    );
+add_action( 'setup_theme',              'bbp_setup_theme',            10    );
+add_action( 'set_current_user',         'bbp_setup_current_user',     10    );
+add_action( 'profile_update',           'bbp_profile_update',         10, 2 ); // user_id and old_user_data
+add_action( 'user_register',            'bbp_user_register',          10    );
+add_action( 'login_form_login',         'bbp_login_form_login',       10    );
+add_action( 'template_redirect',        'bbp_template_redirect',      8     ); // Before BuddyPress's 10 [BB2225]
+add_action( 'widgets_init',             'bbp_widgets_init',           10    );
+add_action( 'wp_roles_init',            'bbp_roles_init',             10    );
+add_action( 'wp_enqueue_scripts',       'bbp_enqueue_scripts',        10    );
+add_action( 'wp_head',                  'bbp_head',                   10    );
+add_action( 'wp_footer',                'bbp_footer',                 10    );
+add_action( 'transition_post_status',   'bbp_transition_post_status', 10, 3 );
+
+/**
+ * bbp_loaded - Attached to 'plugins_loaded' above
+ *
+ * Attach various loader actions to the bbp_loaded action.
+ * The load order helps to execute code at the correct time.
+ *                                                         v---Load order
+ */
+add_action( 'bbp_loaded', 'bbp_constants',                 2  );
+add_action( 'bbp_loaded', 'bbp_boot_strap_globals',        4  );
+add_action( 'bbp_loaded', 'bbp_includes',                  6  );
+add_action( 'bbp_loaded', 'bbp_setup_globals',             8  );
+add_action( 'bbp_loaded', 'bbp_setup_option_filters',      10 );
+add_action( 'bbp_loaded', 'bbp_setup_user_option_filters', 12 );
+add_action( 'bbp_loaded', 'bbp_pre_load_options',          14 );
+
+/**
+ * bbp_init - Attached to 'init' above
+ *
+ * Attach various initialization actions to the init action.
+ * The load order helps to execute code at the correct time.
+ *                                               v---Load order
+ */
+add_action( 'bbp_init', 'bbp_load_textdomain',   0   );
+add_action( 'bbp_init', 'bbp_register',          10  );
+add_action( 'bbp_init', 'bbp_add_rewrite_tags',  20  );
+add_action( 'bbp_init', 'bbp_add_rewrite_rules', 30  );
+add_action( 'bbp_init', 'bbp_add_permastructs',  40  );
+add_action( 'bbp_init', 'bbp_setup_engagements', 50  );
+add_action( 'bbp_init', 'bbp_ready',             999 );
+
+/**
+ * bbp_setup_theme - Attached to 'setup_theme' above
+ *
+ * Attach various theme related actions to the setup_theme action.
+ * The load order helps to execute code at the correct time.
+ *                                                            v---Load order
+ */
+add_action( 'bbp_setup_theme', 'bbp_register_theme_packages', 2 ); // Lower than 5
+
+/**
+ * bbp_roles_init - Attached to 'wp_roles_init' above
+ *
+ * Attach various role related actions to the wp_roles_init action.
+ * The load order helps to execute code at the correct time.
+ *                                                    v---Load order
+ */
+add_action( 'bbp_roles_init', 'bbp_add_forums_roles', 8 );
+
+/**
+ * When setting up the current user, make sure they have a role for the forums.
+ *
+ * This is multisite aware, thanks to bbp_filter_user_roles_option(), hooked to
+ * the 'bbp_loaded' action above.
+ */
+add_action( 'bbp_setup_current_user', 'bbp_set_current_user_default_role' );
+
+/**
+ * bbp_register - Attached to 'init' above on 0 priority
+ *
+ * Attach various initialization actions early to the init action.
+ * The load order helps to execute code at the correct time.
+ *                                                         v---Load order
+ */
+add_action( 'bbp_register', 'bbp_register_post_types',     2  );
+add_action( 'bbp_register', 'bbp_register_post_statuses',  4  );
+add_action( 'bbp_register', 'bbp_register_taxonomies',     6  );
+add_action( 'bbp_register', 'bbp_register_views',          8  );
+add_action( 'bbp_register', 'bbp_register_shortcodes',     10 );
+add_action( 'bbp_register', 'bbp_register_meta',           12 );
+
+// Autoembeds
+add_action( 'bbp_init', 'bbp_reply_content_autoembed', 8 );
+add_action( 'bbp_init', 'bbp_topic_content_autoembed', 8 );
+
+/**
+ * bbp_ready - attached to end 'bbp_init' above
+ *
+ * Attach actions to the ready action after bbPress has fully initialized.
+ * The load order helps to execute code at the correct time.
+ *                                                v---Load order
+ */
+add_action( 'bbp_ready',  'bbp_setup_akismet',    2  ); // Spam prevention for topics and replies
+add_action( 'bp_include', 'bbp_setup_buddypress', 10 ); // Social network integration
+
+// Try to load the bbpress-functions.php file from the active themes
+add_action( 'bbp_after_setup_theme', 'bbp_load_theme_functions', 10 );
+
+// Widgets
+add_action( 'bbp_widgets_init', array( 'BBP_Login_Widget',   'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Views_Widget',   'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Search_Widget',  'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Forums_Widget',  'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Topics_Widget',  'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Replies_Widget', 'register_widget' ), 10 );
+add_action( 'bbp_widgets_init', array( 'BBP_Stats_Widget',   'register_widget' ), 10 );
+
+// Notices
+add_action( 'bbp_template_notices', 'bbp_template_notices',                20 );
+add_action( 'bbp_template_notices', 'bbp_login_notices'                       );
+add_action( 'bbp_template_notices', 'bbp_topic_notices'                       );
+add_action( 'bbp_template_notices', 'bbp_notice_edit_user_success'            );
+add_action( 'bbp_template_notices', 'bbp_notice_edit_user_pending_email'      );
+add_action( 'bbp_template_notices', 'bbp_notice_edit_user_is_super_admin', 2  );
+
+// Always exclude private/hidden forums if needed
+add_action( 'pre_get_posts', 'bbp_pre_get_posts_normalize_forum_visibility', 4 );
+
+// Before Delete/Trash/Untrash Forum
+add_action( 'wp_trash_post',      'bbp_trash_forum'   );
+add_action( 'trash_post',         'bbp_trash_forum'   );
+add_action( 'untrash_post',       'bbp_untrash_forum' );
+add_action( 'before_delete_post', 'bbp_delete_forum'  );
+
+// After Deleted/Trashed/Untrashed Forum
+add_action( 'trashed_post',   'bbp_trashed_forum'   );
+add_action( 'untrashed_post', 'bbp_untrashed_forum' );
+add_action( 'deleted_post',   'bbp_deleted_forum'   );
+
+// Auto trash/untrash/delete a forums topics
+add_action( 'bbp_delete_forum',  'bbp_delete_forum_topics',  10 );
+add_action( 'bbp_trash_forum',   'bbp_trash_forum_topics',   10 );
+add_action( 'bbp_untrash_forum', 'bbp_untrash_forum_topics', 10 );
+
+// New/Edit Forum
+add_action( 'bbp_new_forum',  'bbp_update_forum', 10 );
+add_action( 'bbp_edit_forum', 'bbp_update_forum', 10 );
+
+// Save forum extra metadata
+add_action( 'bbp_new_forum_post_extras',         'bbp_save_forum_extras', 2 );
+add_action( 'bbp_edit_forum_post_extras',        'bbp_save_forum_extras', 2 );
+add_action( 'bbp_forum_attributes_metabox_save', 'bbp_save_forum_extras', 2 );
+
+// New/Edit Reply
+add_action( 'bbp_new_reply',  'bbp_update_reply', 10, 7 );
+add_action( 'bbp_edit_reply', 'bbp_update_reply', 10, 7 );
+
+// Before Delete/Trash/Untrash Reply
+add_action( 'wp_trash_post',      'bbp_trash_reply'   );
+add_action( 'trash_post',         'bbp_trash_reply'   );
+add_action( 'untrash_post',       'bbp_untrash_reply' );
+add_action( 'before_delete_post', 'bbp_delete_reply'  );
+
+// After Deleted/Trashed/Untrashed Reply
+add_action( 'trashed_post',   'bbp_trashed_reply'   );
+add_action( 'untrashed_post', 'bbp_untrashed_reply' );
+add_action( 'deleted_post',   'bbp_deleted_reply'   );
+
+// New/Edit Topic
+add_action( 'bbp_new_topic',  'bbp_update_topic', 10, 5 );
+add_action( 'bbp_edit_topic', 'bbp_update_topic', 10, 5 );
+
+// Split/Merge Topic
+add_action( 'bbp_merged_topic',     'bbp_merge_topic_count', 1, 3 );
+add_action( 'bbp_post_split_topic', 'bbp_split_topic_count', 1, 3 );
+
+// Move Reply
+add_action( 'bbp_post_move_reply', 'bbp_move_reply_count', 1, 3 );
+
+// Before Delete/Trash/Untrash Topic
+add_action( 'wp_trash_post',      'bbp_trash_topic'   );
+add_action( 'trash_post',         'bbp_trash_topic'   );
+add_action( 'untrash_post',       'bbp_untrash_topic' );
+add_action( 'before_delete_post', 'bbp_delete_topic'  );
+
+// After Deleted/Trashed/Untrashed Topic
+add_action( 'trashed_post',   'bbp_trashed_topic'   );
+add_action( 'untrashed_post', 'bbp_untrashed_topic' );
+add_action( 'deleted_post',   'bbp_deleted_topic'   );
+
+// Favorites
+add_action( 'bbp_spam_topic',   'bbp_remove_topic_from_all_favorites' );
+add_action( 'bbp_trash_topic',  'bbp_remove_topic_from_all_favorites' );
+add_action( 'bbp_delete_topic', 'bbp_remove_topic_from_all_favorites' );
+
+// Subscriptions
+add_action( 'bbp_spam_topic',   'bbp_remove_topic_from_all_subscriptions' );
+add_action( 'bbp_trash_topic',  'bbp_remove_topic_from_all_subscriptions' );
+add_action( 'bbp_delete_topic', 'bbp_remove_topic_from_all_subscriptions' );
+add_action( 'bbp_trash_forum',  'bbp_remove_forum_from_all_subscriptions' );
+add_action( 'bbp_delete_forum', 'bbp_remove_forum_from_all_subscriptions' );
+
+// Subscription notifications
+add_action( 'bbp_new_reply',    'bbp_notify_topic_subscribers', 11, 5 );
+add_action( 'bbp_new_topic',    'bbp_notify_forum_subscribers', 11, 4 );
+
+// Sticky
+add_action( 'bbp_stick_topic',     'bbp_unstick_topic' );
+add_action( 'bbp_unapprove_topic', 'bbp_unstick_topic' );
+add_action( 'bbp_spam_topic',      'bbp_unstick_topic' );
+add_action( 'bbp_trash_topic',     'bbp_unstick_topic' );
+add_action( 'bbp_delete_topic',    'bbp_unstick_topic' );
+
+// Update topic branch
+add_action( 'bbp_trashed_topic',    'bbp_update_topic_walker' );
+add_action( 'bbp_untrashed_topic',  'bbp_update_topic_walker' );
+add_action( 'bbp_deleted_topic',    'bbp_update_topic_walker' );
+add_action( 'bbp_spammed_topic',    'bbp_update_topic_walker' );
+add_action( 'bbp_unspammed_topic',  'bbp_update_topic_walker' );
+add_action( 'bbp_approved_topic',   'bbp_update_topic_walker' );
+add_action( 'bbp_unapproved_topic', 'bbp_update_topic_walker' );
+
+// Update reply branch
+add_action( 'bbp_trashed_reply',    'bbp_update_reply_walker' );
+add_action( 'bbp_untrashed_reply',  'bbp_update_reply_walker' );
+add_action( 'bbp_deleted_reply',    'bbp_update_reply_walker' );
+add_action( 'bbp_spammed_reply',    'bbp_update_reply_walker' );
+add_action( 'bbp_unspammed_reply',  'bbp_update_reply_walker' );
+add_action( 'bbp_approved_reply',   'bbp_update_reply_walker' );
+add_action( 'bbp_unapproved_reply', 'bbp_update_reply_walker' );
+
+// Update forum reply counts
+add_action( 'bbp_new_reply',        'bbp_increase_forum_reply_count' );
+add_action( 'bbp_untrashed_reply',  'bbp_increase_forum_reply_count' );
+add_action( 'bbp_unspammed_reply',  'bbp_increase_forum_reply_count' );
+add_action( 'bbp_approved_reply',   'bbp_increase_forum_reply_count' );
+add_action( 'bbp_trash_reply',      'bbp_decrease_forum_reply_count' );
+add_action( 'bbp_spam_reply',       'bbp_decrease_forum_reply_count' );
+add_action( 'bbp_unapprove_reply',  'bbp_decrease_forum_reply_count' );
+
+// Update forum hidden reply counts
+add_action( 'bbp_trashed_reply',    'bbp_increase_forum_reply_count_hidden' );
+add_action( 'bbp_spammed_reply',    'bbp_increase_forum_reply_count_hidden' );
+add_action( 'bbp_unapproved_reply', 'bbp_increase_forum_reply_count_hidden' );
+add_action( 'bbp_untrash_reply',    'bbp_decrease_forum_reply_count_hidden' );
+add_action( 'bbp_unspam_reply',     'bbp_decrease_forum_reply_count_hidden' );
+add_action( 'bbp_approve_reply',    'bbp_decrease_forum_reply_count_hidden' );
+add_action( 'bbp_delete_reply',     'bbp_decrease_forum_reply_count_hidden' );
+
+// Update forum topic counts
+add_action( 'bbp_new_topic',        'bbp_increase_forum_topic_count' );
+add_action( 'bbp_untrashed_topic',  'bbp_increase_forum_topic_count' );
+add_action( 'bbp_unspammed_topic',  'bbp_increase_forum_topic_count' );
+add_action( 'bbp_approved_topic',   'bbp_increase_forum_topic_count' );
+add_action( 'bbp_trash_topic',      'bbp_decrease_forum_topic_count' );
+add_action( 'bbp_spam_topic',       'bbp_decrease_forum_topic_count' );
+add_action( 'bbp_unapprove_topic',  'bbp_decrease_forum_topic_count' );
+
+// Update forum hidden topic counts
+add_action( 'bbp_trashed_topic',    'bbp_increase_forum_topic_count_hidden' );
+add_action( 'bbp_spammed_topic',    'bbp_increase_forum_topic_count_hidden' );
+add_action( 'bbp_unapproved_topic', 'bbp_increase_forum_topic_count_hidden' );
+add_action( 'bbp_untrash_topic',    'bbp_decrease_forum_topic_count_hidden' );
+add_action( 'bbp_unspam_topic',     'bbp_decrease_forum_topic_count_hidden' );
+add_action( 'bbp_approve_topic',    'bbp_decrease_forum_topic_count_hidden' );
+add_action( 'bbp_delete_topic',     'bbp_decrease_forum_topic_count_hidden' );
+
+// Update topic reply counts
+add_action( 'bbp_new_reply',        'bbp_increase_topic_reply_count' );
+add_action( 'bbp_untrashed_reply',  'bbp_increase_topic_reply_count' );
+add_action( 'bbp_unspammed_reply',  'bbp_increase_topic_reply_count' );
+add_action( 'bbp_approved_reply',   'bbp_increase_topic_reply_count' );
+add_action( 'bbp_trash_reply',      'bbp_decrease_topic_reply_count' );
+add_action( 'bbp_spam_reply',       'bbp_decrease_topic_reply_count' );
+add_action( 'bbp_unapprove_reply',  'bbp_decrease_topic_reply_count' );
+
+// Update topic hidden reply counts
+add_action( 'bbp_trashed_reply',    'bbp_increase_topic_reply_count_hidden' );
+add_action( 'bbp_unapproved_reply', 'bbp_increase_topic_reply_count_hidden' );
+add_action( 'bbp_spammed_reply',    'bbp_increase_topic_reply_count_hidden' );
+add_action( 'bbp_untrash_reply',    'bbp_decrease_topic_reply_count_hidden' );
+add_action( 'bbp_unspam_reply',     'bbp_decrease_topic_reply_count_hidden' );
+add_action( 'bbp_approve_reply',    'bbp_decrease_topic_reply_count_hidden' );
+add_action( 'bbp_delete_reply',     'bbp_decrease_topic_reply_count_hidden' );
+
+// Update forum reply counts for approved/unapproved topics
+add_action( 'bbp_approved_topic',   'bbp_approved_unapproved_topic_update_forum_reply_count' );
+add_action( 'bbp_unapproved_topic', 'bbp_approved_unapproved_topic_update_forum_reply_count' );
+
+// Users topic & reply counts
+add_action( 'bbp_new_topic',     'bbp_increase_user_topic_count' );
+add_action( 'bbp_new_reply',     'bbp_increase_user_reply_count' );
+add_action( 'bbp_untrash_topic', 'bbp_increase_user_topic_count' );
+add_action( 'bbp_untrash_reply', 'bbp_increase_user_reply_count' );
+add_action( 'bbp_unspam_topic',  'bbp_increase_user_topic_count' );
+add_action( 'bbp_unspam_reply',  'bbp_increase_user_reply_count' );
+add_action( 'bbp_trash_topic',   'bbp_decrease_user_topic_count' );
+add_action( 'bbp_trash_reply',   'bbp_decrease_user_reply_count' );
+add_action( 'bbp_spam_topic',    'bbp_decrease_user_topic_count' );
+add_action( 'bbp_spam_reply',    'bbp_decrease_user_reply_count' );
+
+// Topic status transition helpers for replies
+add_action( 'bbp_trash_topic',   'bbp_trash_topic_replies'   );
+add_action( 'bbp_untrash_topic', 'bbp_untrash_topic_replies' );
+add_action( 'bbp_delete_topic',  'bbp_delete_topic_replies'  );
+add_action( 'bbp_spam_topic',    'bbp_spam_topic_replies'    );
+add_action( 'bbp_unspam_topic',  'bbp_unspam_topic_replies'  );
+
+// Topic engagements on user creation
+add_action( 'bbp_new_topic', 'bbp_update_topic_engagements', 20 );
+add_action( 'bbp_new_reply', 'bbp_update_topic_engagements', 20 );
+
+add_action( 'bbp_new_reply', 'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_new_topic', 'bbp_update_topic_voice_count', 30 );
+
+// Topic/reply counts on code insert (unit tests)
+add_action( 'bbp_insert_topic', 'bbp_insert_topic_update_counts', 10, 2 );
+add_action( 'bbp_insert_reply', 'bbp_insert_reply_update_counts', 10, 3 );
+
+// Topic engagements on code insert (unit tests)
+add_action( 'bbp_insert_topic', 'bbp_update_topic_engagements', 20 );
+add_action( 'bbp_insert_reply', 'bbp_update_topic_engagements', 20 );
+
+// Topic engagement counts on code insert (unit tests)
+add_action( 'bbp_insert_topic', 'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_insert_reply', 'bbp_update_topic_voice_count', 30 );
+
+// Recalculate engagements
+add_action( 'bbp_trashed_reply',    'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_untrashed_reply',  'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_spammed_reply',    'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_unspammed_reply',  'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_approved_reply',   'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_unapproved_reply', 'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_deleted_reply',    'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_trashed_topic',    'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_untrashed_topic',  'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_spammed_topic',    'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_unspammed_topic',  'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_approved_topic',   'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_unapproved_topic', 'bbp_recalculate_topic_engagements' );
+add_action( 'bbp_deleted_topic',    'bbp_recalculate_topic_engagements' );
+
+// Update engagement counts
+add_action( 'bbp_trashed_reply',    'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_untrashed_reply',  'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_spammed_reply',    'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_unspammed_reply',  'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_approved_reply',   'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_unapproved_reply', 'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_deleted_reply',    'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_trashed_topic',    'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_untrashed_topic',  'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_spammed_topic',    'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_unspammed_topic',  'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_approved_topic',   'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_unapproved_topic', 'bbp_update_topic_voice_count', 30 );
+add_action( 'bbp_deleted_topic',    'bbp_update_topic_voice_count', 30 );
+
+// User status
+// @todo make these sub-actions
+add_action( 'make_ham_user',  'bbp_make_ham_user'  );
+add_action( 'make_spam_user', 'bbp_make_spam_user' );
+
+// User role
+add_action( 'bbp_profile_update', 'bbp_profile_update_role' );
+
+// Hook WordPress admin actions to bbPress profiles on save
+add_action( 'bbp_user_edit_after', 'bbp_user_edit_after' );
+
+// Clean bbPress post caches when WordPress's is cleaned
+add_action( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
+
+// User Registration
+add_action( 'added_existing_user', 'bbp_user_add_role_on_register', 10, 1 );
+add_action( 'bbp_user_register',   'bbp_user_add_role_on_register', 10, 1 );
+
+// Invite a New User
+add_action( 'invite_user',         'bbp_user_add_role_on_invite',   10, 3 );
+
+// Multisite Activation (does not work in wp-activate.php)
+add_action( 'wpmu_activate_user',  'bbp_user_add_role_on_activate', 10, 3 );
+
+/**
+ * bbPress needs to redirect the user around in a few different circumstances:
+ *
+ * 1. POST and GET requests
+ * 2. Accessing private or hidden content (forums/topics/replies)
+ * 3. Editing forums, topics, replies, users, and tags
+ * 4. bbPress specific AJAX requests
+ */
+add_action( 'bbp_template_redirect', 'bbp_forum_enforce_blocked', 1  );
+add_action( 'bbp_template_redirect', 'bbp_forum_enforce_hidden',  1  );
+add_action( 'bbp_template_redirect', 'bbp_forum_enforce_private', 1  );
+add_action( 'bbp_template_redirect', 'bbp_post_request',          10 );
+add_action( 'bbp_template_redirect', 'bbp_get_request',           10 );
+add_action( 'bbp_template_redirect', 'bbp_check_user_edit',       10 );
+add_action( 'bbp_template_redirect', 'bbp_check_forum_edit',      10 );
+add_action( 'bbp_template_redirect', 'bbp_check_topic_edit',      10 );
+add_action( 'bbp_template_redirect', 'bbp_check_reply_edit',      10 );
+add_action( 'bbp_template_redirect', 'bbp_check_topic_tag_edit',  10 );
+
+// Must be after bbp_template_include_theme_compat
+add_action( 'bbp_template_redirect', 'bbp_remove_adjacent_posts', 10 );
+
+// Theme-side POST requests
+add_action( 'bbp_post_request', 'bbp_do_ajax',                1  );
+add_action( 'bbp_post_request', 'bbp_edit_topic_tag_handler', 1  );
+add_action( 'bbp_post_request', 'bbp_edit_user_handler',      1  );
+add_action( 'bbp_post_request', 'bbp_edit_forum_handler',     1  );
+add_action( 'bbp_post_request', 'bbp_edit_reply_handler',     1  );
+add_action( 'bbp_post_request', 'bbp_edit_topic_handler',     1  );
+add_action( 'bbp_post_request', 'bbp_merge_topic_handler',    1  );
+add_action( 'bbp_post_request', 'bbp_split_topic_handler',    1  );
+add_action( 'bbp_post_request', 'bbp_move_reply_handler',     1  );
+add_action( 'bbp_post_request', 'bbp_new_forum_handler',      10 );
+add_action( 'bbp_post_request', 'bbp_new_reply_handler',      10 );
+add_action( 'bbp_post_request', 'bbp_new_topic_handler',      10 );
+
+// Theme-side GET requests
+add_action( 'bbp_get_request', 'bbp_toggle_topic_handler',        1  );
+add_action( 'bbp_get_request', 'bbp_toggle_reply_handler',        1  );
+add_action( 'bbp_get_request', 'bbp_favorites_handler',           1  );
+add_action( 'bbp_get_request', 'bbp_subscriptions_handler',       1  );
+add_action( 'bbp_get_request', 'bbp_user_email_change_handler',   1  );
+add_action( 'bbp_get_request', 'bbp_search_results_redirect',     10 );
+
+// Maybe convert the users password
+add_action( 'bbp_login_form_login', 'bbp_user_maybe_convert_pass' );
Index: /tags/2.6.6/src/includes/core/cache.php
===================================================================
--- /tags/2.6.6/src/includes/core/cache.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/cache.php	(revision 7162)
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * bbPress Cache Helpers
+ *
+ * Helper functions used to communicate with WordPress's various caches. Many
+ * of these functions are used to work around specific WordPress nuances. They
+ * are subject to changes, tweaking, and will need iteration as performance
+ * improvements are made to WordPress core.
+ *
+ * @package bbPress
+ * @subpackage Cache
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Helpers *******************************************************************/
+
+/**
+ * Skip invalidation of child post content when editing a parent.
+ *
+ * This prevents invalidating caches for topics and replies when editing a forum
+ * or a topic. Without this in place, WordPress will attempt to invalidate all
+ * child posts whenever a parent post is modified. This can cause thousands of
+ * cache invalidations to occur on a single edit, which is no good for anyone.
+ *
+ * @since 2.1.0 bbPress (r4011)
+ *
+ * @package bbPress
+ * @subpackage Cache
+ */
+class BBP_Skip_Children {
+
+	/**
+	 * @var int Post ID being updated
+	 */
+	private $updating_post = 0;
+
+	/**
+	 * @var bool The original value of $_wp_suspend_cache_invalidation global
+	 */
+	private $original_cache_invalidation = false;
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Hook into the 'pre_post_update' action.
+	 *
+	 * @since 2.1.0 bbPress (r4011)
+	 */
+	public function __construct() {
+		add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
+	}
+
+	/**
+	 * Only clean post caches for main bbPress posts.
+	 *
+	 * Check that the post being updated is a bbPress post type, saves the
+	 * post ID to be used later, and adds an action to 'clean_post_cache' that
+	 * prevents child post caches from being cleared.
+	 *
+	 * @since 2.1.0 bbPress (r4011)
+	 *
+	 * @param int $post_id The post ID being updated
+	 * @return If invalid post data
+	 */
+	public function pre_post_update( $post_id = 0 ) {
+
+		// Bail if post ID is not a bbPress post type
+		if ( empty( $post_id ) || ! bbp_is_custom_post_type( $post_id ) ) {
+			return;
+		}
+
+		// Store the $post_id
+		$this->updating_post = $post_id;
+
+		// Skip related post cache invalidation. This prevents invalidating the
+		// caches of the child posts when there is no reason to do so.
+		add_action( 'clean_post_cache', array( $this, 'skip_related_posts' ) );
+	}
+
+	/**
+	 * Skip cache invalidation of related posts if the post ID being invalidated
+	 * is not the one that was just updated.
+	 *
+	 * @since 2.1.0 bbPress (r4011)
+	 *
+	 * @param int $post_id The post ID of the cache being invalidated
+	 * @return If invalid post data
+	 */
+	public function skip_related_posts( $post_id = 0 ) {
+
+		// Bail if this post is not the current bbPress post
+		if ( empty( $post_id ) || ( $this->updating_post !== $post_id ) ) {
+			return;
+		}
+
+		// Stash the current cache invalidation value in a variable, so we can
+		// restore back to it nicely in the future.
+		global $_wp_suspend_cache_invalidation;
+
+		$this->original_cache_invalidation = $_wp_suspend_cache_invalidation;
+
+		// Turn off cache invalidation
+		wp_suspend_cache_invalidation( true );
+
+		// Restore cache invalidation
+		add_action( 'wp_insert_post', array( $this, 'restore_cache_invalidation' ) );
+	}
+
+	/**
+	 * Restore the cache invalidation to its previous value.
+	 *
+	 * @since 2.1.0 bbPress (r4011)
+	 */
+	public function restore_cache_invalidation() {
+		wp_suspend_cache_invalidation( $this->original_cache_invalidation );
+	}
+}
+new BBP_Skip_Children();
+
+/** General *******************************************************************/
+
+/**
+ * Will clean a post in the cache.
+ *
+ * Will call to clean the term object cache associated with the post ID.
+ *
+ * @since 2.1.0 bbPress (r4040)
+ * @since 2.6.0 bbPress (r6053) Introduced the `$post_id` parameter.
+ *
+ * @param int     $post_id The post id.
+ * @param WP_Post $post    The WP_Post object.
+ */
+function bbp_clean_post_cache( $post_id = null, $post = null ) {
+
+	// Child query types to clean
+	$post_types = array(
+		bbp_get_forum_post_type(),
+		bbp_get_topic_post_type(),
+		bbp_get_reply_post_type()
+	);
+
+	// Bail if not a bbPress post type
+	if ( ! in_array( $post->post_type, $post_types, true ) ) {
+		return;
+	}
+
+	/**
+	 * Fires immediately after the given post cache is cleaned.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param int     $post_id Post ID.
+	 * @param WP_Post $post    Post object.
+	 */
+	do_action( 'bbp_clean_post_cache', $post->ID, $post );
+
+	// Invalidate parent caches
+	if ( ! empty( $post->post_parent ) ) {
+		clean_post_cache( $post->post_parent );
+
+	// Only bump `last_changed` when forum-root is reached
+	} else {
+		wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
+	}
+}
Index: /tags/2.6.6/src/includes/core/capabilities.php
===================================================================
--- /tags/2.6.6/src/includes/core/capabilities.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/capabilities.php	(revision 7162)
@@ -0,0 +1,525 @@
+<?php
+
+/**
+ * bbPress Capabilites
+ *
+ * The functions in this file are used primarily as convenient wrappers for
+ * capability output in user profiles. This includes mapping capabilities and
+ * groups to human readable strings,
+ *
+ * @package bbPress
+ * @subpackage Capabilities
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Mapping *******************************************************************/
+
+/**
+ * Returns an array of capabilities based on the role that is being requested.
+ *
+ * @since 2.0.0 bbPress (r2994)
+ *
+ * @todo Map all of these and deprecate
+ *
+ * @param string $role Optional. Defaults to The role to load caps for
+ *
+ * @return array Capabilities for $role
+ */
+function bbp_get_caps_for_role( $role = '' ) {
+
+	// Which role are we looking for?
+	switch ( $role ) {
+
+		// Keymaster
+		case bbp_get_keymaster_role() :
+			$caps = array(
+
+				// Keymasters only
+				'keep_gate'             => true,
+
+				// Primary caps
+				'spectate'              => true,
+				'participate'           => true,
+				'moderate'              => true,
+				'throttle'              => true,
+				'view_trash'            => true,
+				'assign_moderators'     => true,
+
+				// Forum caps
+				'publish_forums'        => true,
+				'edit_forums'           => true,
+				'edit_others_forums'    => true,
+				'delete_forums'         => true,
+				'delete_others_forums'  => true,
+				'read_private_forums'   => true,
+				'read_hidden_forums'    => true,
+
+				// Topic caps
+				'publish_topics'        => true,
+				'edit_topics'           => true,
+				'edit_others_topics'    => true,
+				'delete_topics'         => true,
+				'delete_others_topics'  => true,
+				'read_private_topics'   => true,
+
+				// Reply caps
+				'publish_replies'       => true,
+				'edit_replies'          => true,
+				'edit_others_replies'   => true,
+				'delete_replies'        => true,
+				'delete_others_replies' => true,
+				'read_private_replies'  => true,
+
+				// Topic tag caps
+				'manage_topic_tags'     => true,
+				'edit_topic_tags'       => true,
+				'delete_topic_tags'     => true,
+				'assign_topic_tags'     => true
+			);
+
+			break;
+
+		// Moderator
+		case bbp_get_moderator_role() :
+			$caps = array(
+
+				// Primary caps
+				'spectate'              => true,
+				'participate'           => true,
+				'moderate'              => true,
+				'throttle'              => true,
+				'view_trash'            => true,
+				'assign_moderators'     => true,
+
+				// Forum caps
+				'publish_forums'        => true,
+				'edit_forums'           => true,
+				'read_private_forums'   => true,
+				'read_hidden_forums'    => true,
+
+				// Topic caps
+				'publish_topics'        => true,
+				'edit_topics'           => true,
+				'edit_others_topics'    => true,
+				'delete_topics'         => true,
+				'delete_others_topics'  => true,
+				'read_private_topics'   => true,
+
+				// Reply caps
+				'publish_replies'       => true,
+				'edit_replies'          => true,
+				'edit_others_replies'   => true,
+				'delete_replies'        => true,
+				'delete_others_replies' => true,
+				'read_private_replies'  => true,
+
+				// Topic tag caps
+				'manage_topic_tags'     => true,
+				'edit_topic_tags'       => true,
+				'delete_topic_tags'     => true,
+				'assign_topic_tags'     => true,
+			);
+
+			break;
+
+		// Spectators can only read
+		case bbp_get_spectator_role()   :
+			$caps = array(
+
+				// Primary caps
+				'spectate'              => true,
+			);
+
+			break;
+
+		// Explicitly blocked
+		case bbp_get_blocked_role() :
+			$caps = array(
+
+				// Primary caps
+				'spectate'              => false,
+				'participate'           => false,
+				'moderate'              => false,
+				'throttle'              => false,
+				'view_trash'            => false,
+
+				// Forum caps
+				'publish_forums'        => false,
+				'edit_forums'           => false,
+				'edit_others_forums'    => false,
+				'delete_forums'         => false,
+				'delete_others_forums'  => false,
+				'read_private_forums'   => false,
+				'read_hidden_forums'    => false,
+
+				// Topic caps
+				'publish_topics'        => false,
+				'edit_topics'           => false,
+				'edit_others_topics'    => false,
+				'delete_topics'         => false,
+				'delete_others_topics'  => false,
+				'read_private_topics'   => false,
+
+				// Reply caps
+				'publish_replies'       => false,
+				'edit_replies'          => false,
+				'edit_others_replies'   => false,
+				'delete_replies'        => false,
+				'delete_others_replies' => false,
+				'read_private_replies'  => false,
+
+				// Topic tag caps
+				'manage_topic_tags'     => false,
+				'edit_topic_tags'       => false,
+				'delete_topic_tags'     => false,
+				'assign_topic_tags'     => false,
+			);
+
+			break;
+
+		// Participant/Default
+		case bbp_get_participant_role() :
+		default :
+			$caps = array(
+
+				// Primary caps
+				'spectate'              => true,
+				'participate'           => true,
+
+				// Forum caps
+				'read_private_forums'   => true,
+
+				// Topic caps
+				'publish_topics'        => true,
+				'edit_topics'           => true,
+
+				// Reply caps
+				'publish_replies'       => true,
+				'edit_replies'          => true,
+
+				// Topic tag caps
+				'assign_topic_tags'     => true,
+			);
+
+			break;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_caps_for_role', $caps, $role );
+}
+
+/**
+ * Adds capabilities to WordPress user roles.
+ *
+ * @since 2.0.0 bbPress (r2608)
+ */
+function bbp_add_caps() {
+
+	// Loop through available roles and add caps
+	foreach ( bbp_get_wp_roles()->role_objects as $role ) {
+		foreach ( bbp_get_caps_for_role( $role->name ) as $cap => $value ) {
+			$role->add_cap( $cap, $value );
+		}
+	}
+
+	do_action( 'bbp_add_caps' );
+}
+
+/**
+ * Removes capabilities from WordPress user roles.
+ *
+ * @since 2.0.0 bbPress (r2608)
+ */
+function bbp_remove_caps() {
+
+	// Loop through available roles and remove caps
+	foreach ( bbp_get_wp_roles()->role_objects as $role ) {
+		foreach ( array_keys( bbp_get_caps_for_role( $role->name ) ) as $cap ) {
+			$role->remove_cap( $cap );
+		}
+	}
+
+	do_action( 'bbp_remove_caps' );
+}
+
+/**
+ * Get the available roles, minus the dynamic roles that come with bbPress
+ *
+ * @since 2.4.0 bbPress (r5064)
+ *
+ * @return array
+ */
+function bbp_get_blog_roles() {
+
+	// Get WordPress's roles (returns $wp_roles global)
+	$wp_roles = bbp_get_wp_roles();
+
+	// Apply the WordPress 'editable_roles' filter to let plugins ride along.
+	//
+	// We use this internally via bbp_filter_blog_editable_roles() to remove
+	// any custom bbPress roles that are added to the global.
+	$the_roles = isset( $wp_roles->roles ) ? $wp_roles->roles : false;
+	$all_roles = apply_filters( 'editable_roles', $the_roles );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_blog_roles', $all_roles, $wp_roles );
+}
+
+/** Forum Roles ***************************************************************/
+
+/**
+ * Add the bbPress roles to the $wp_roles global.
+ *
+ * We do this to avoid adding these values to the database.
+ *
+ * Note: bbPress is purposely assertive here, overwriting any keys & values
+ * that may already exist in the $wp_roles array.
+ *
+ * @since 2.2.0 bbPress (r4290)
+ *
+ * @param WP_Roles $wp_roles The array of WP_Role objects that was initialized
+ *
+ * @return WP_Roles The main $wp_roles global
+ */
+function bbp_add_forums_roles( $wp_roles = null ) {
+
+	// Get the dynamic roles
+	$bbp_roles = bbp_get_dynamic_roles();
+
+	// Loop through dynamic roles and add them to the $wp_roles array
+	foreach ( $bbp_roles as $role_id => $details ) {
+		$wp_roles->roles[ $role_id ]        = $details;
+		$wp_roles->role_objects[ $role_id ] = new WP_Role( $role_id, $details['capabilities'] );
+		$wp_roles->role_names[ $role_id ]   = $details['name'];
+	}
+
+	// Return the modified $wp_roles array
+	return $wp_roles;
+}
+
+/**
+ * Helper function to add filter to option_wp_user_roles
+ *
+ * @since 2.2.0 bbPress (r4363)
+ * @deprecated 2.6.0 bbPress (r6105)
+ *
+ * @see _bbp_reinit_dynamic_roles()
+ */
+function bbp_filter_user_roles_option() {
+	$role_key = bbp_db()->prefix . 'user_roles';
+
+	add_filter( 'option_' . $role_key, '_bbp_reinit_dynamic_roles' );
+}
+
+/**
+ * This is necessary because in a few places (noted below) WordPress initializes
+ * a blog's roles directly from the database option. When this happens, the
+ * $wp_roles global gets flushed, causing a user to magically lose any
+ * dynamically assigned roles or capabilities when $current_user in refreshed.
+ *
+ * Because dynamic multiple roles is a new concept in WordPress, we work around
+ * it here for now, knowing that improvements will come to WordPress core later.
+ *
+ * Also note that if using the $wp_user_roles global non-database approach,
+ * bbPress does not have an intercept point to add its dynamic roles.
+ *
+ * @see bbp_switch_to_site()
+ * @see bbp_restore_current_site()
+ * @see WP_Roles::_init()
+ *
+ * @since 2.2.0 bbPress (r4363)
+ * @deprecated 2.6.0 bbPress (r6105)
+ *
+ * @internal Used by bbPress to reinitialize dynamic roles on blog switch
+ *
+ * @param array $roles
+ * @return array Combined array of database roles and dynamic bbPress roles
+ */
+function _bbp_reinit_dynamic_roles( $roles = array() ) {
+	foreach ( bbp_get_dynamic_roles() as $role_id => $details ) {
+		$roles[ $role_id ] = $details;
+	}
+	return $roles;
+}
+
+/**
+ * Fetch a filtered list of forum roles that the current user is
+ * allowed to have.
+ *
+ * Simple function who's main purpose is to allow filtering of the
+ * list of forum roles so that plugins can remove inappropriate ones depending
+ * on the situation or user making edits.
+ *
+ * Specifically because without filtering, anyone with the edit_users
+ * capability can edit others to be administrators, even if they are
+ * only editors or authors. This filter allows admins to delegate
+ * user management.
+ *
+ * @since 2.2.0 bbPress (r4284)
+ * @since 2.6.0 bbPress (r6117) Use bbpress()->roles
+ *
+ * @return array
+ */
+function bbp_get_dynamic_roles() {
+
+	// Defaults
+	$to_array = array();
+	$roles    = bbpress()->roles;
+
+	// Convert WP_Roles objects to arrays
+	foreach ( $roles as $role_id => $wp_role ) {
+		$to_array[ $role_id ] = (array) $wp_role;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_dynamic_roles', $to_array, $roles );
+}
+
+/**
+ * Gets a translated role name from a role ID
+ *
+ * @since 2.3.0 bbPress (r4792)
+ * @since 2.6.0 bbPress (r6117) Use bbp_translate_user_role()
+ *
+ * @param string $role_id
+ * @return string Translated role name
+ */
+function bbp_get_dynamic_role_name( $role_id = '' ) {
+	$roles = bbp_get_dynamic_roles();
+	$role  = isset( $roles[ $role_id ] )
+		? bbp_translate_user_role( $roles[ $role_id ]['name'] )
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_dynamic_role_name', $role, $role_id, $roles );
+}
+
+/**
+ * Removes the bbPress roles from the editable roles array
+ *
+ * This used to use array_diff_assoc() but it randomly broke before 2.2 release.
+ * Need to research what happened, and if there's a way to speed this up.
+ *
+ * @since 2.2.0 bbPress (r4303)
+ *
+ * @param array $all_roles All registered roles
+ * @return array
+ */
+function bbp_filter_blog_editable_roles( $all_roles = array() ) {
+
+	// Loop through bbPress roles
+	foreach ( array_keys( bbp_get_dynamic_roles() ) as $bbp_role ) {
+
+		// Loop through WordPress roles
+		foreach ( array_keys( $all_roles ) as $wp_role ) {
+
+			// If keys match, unset
+			if ( $wp_role === $bbp_role ) {
+				unset( $all_roles[ $wp_role ] );
+			}
+		}
+	}
+
+	return $all_roles;
+}
+
+/**
+ * The keymaster role for bbPress users
+ *
+ * @since 2.2.0 bbPress (r4284)
+ *
+ * @return string
+ */
+function bbp_get_keymaster_role() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_keymaster_role', 'bbp_keymaster' );
+}
+
+/**
+ * The moderator role for bbPress users
+ *
+ * @since 2.0.0 bbPress (r3410)
+ *
+ * @return string
+ */
+function bbp_get_moderator_role() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_moderator_role', 'bbp_moderator' );
+}
+
+/**
+ * The participant role for registered user that can participate in forums
+ *
+ * @since 2.0.0 bbPress (r3410)
+ *
+ * @return string
+ */
+function bbp_get_participant_role() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_participant_role', 'bbp_participant' );
+}
+
+/**
+ * The spectator role is for registered users without any capabilities
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @return string
+ */
+function bbp_get_spectator_role() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_spectator_role', 'bbp_spectator' );
+}
+
+/**
+ * The blocked role is for registered users that cannot spectate or participate
+ *
+ * @since 2.2.0 bbPress (r4284)
+ *
+ * @return string
+ */
+function bbp_get_blocked_role() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_blocked_role', 'bbp_blocked' );
+}
+
+/**
+ * Adds bbPress-specific user roles.
+ *
+ * @since 2.0.0 bbPress (r2741)
+ *
+ * @deprecated 2.2.0 bbPress (r4164)
+ */
+function bbp_add_roles() {
+	_doing_it_wrong( 'bbp_add_roles', esc_html__( 'Editable forum roles no longer exist.', 'bbpress' ), '2.2' );
+}
+
+/**
+ * Removes bbPress-specific user roles from the `wp_user_roles` array.
+ *
+ * This is currently only used when updating, uninstalling, or resetting bbPress.
+ *
+ * @see	bbp_admin_reset_handler()
+ * @see bbp_do_uninstall()
+ * @see bbp_version_updater()
+ *
+ * @since 2.0.0 bbPress (r2741)
+ */
+function bbp_remove_roles() {
+
+	// Remove the bbPress roles
+	foreach ( array_keys( bbp_get_dynamic_roles() ) as $bbp_role ) {
+		remove_role( $bbp_role );
+	}
+
+	// Some early adopters may have a deprecated visitor role. It was later
+	// replaced by the Spectator role.
+	remove_role( 'bbp_visitor' );
+}
Index: /tags/2.6.6/src/includes/core/extend.php
===================================================================
--- /tags/2.6.6/src/includes/core/extend.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/extend.php	(revision 7162)
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * bbPress Extentions
+ *
+ * There's a world of really cool plugins out there, and bbPress comes with
+ * support for some of the most popular ones.
+ *
+ * @package bbPress
+ * @subpackage Extend
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Loads Akismet inside the bbPress global class
+ *
+ * @since 2.0.0 bbPress (r3277)
+ *
+ * @return If bbPress is not active
+ */
+function bbp_setup_akismet() {
+
+	// Bail if no akismet
+	if ( ! defined( 'AKISMET_VERSION' ) ) {
+		return;
+	}
+
+	// Bail if Akismet is turned off
+	if ( ! bbp_is_akismet_active() ) {
+		return;
+	}
+
+	// Include the Akismet Component
+	require_once bbpress()->includes_dir . 'extend/akismet.php';
+
+	// Instantiate Akismet for bbPress
+	bbpress()->extend->akismet = new BBP_Akismet();
+}
+
+/**
+ * Requires and creates the BuddyPress extension, and adds component creation
+ * action to bp_init hook. @see bbp_setup_buddypress_component()
+ *
+ * @since 2.0.0 bbPress (r3395)
+ *
+ * @return If BuddyPress is not active
+ */
+function bbp_setup_buddypress() {
+
+	if ( ! function_exists( 'buddypress' ) ) {
+
+		/**
+		 * Helper for BuddyPress 1.6 and earlier
+		 *
+		 * @since 2.2.0 bbPress (r4395)
+		 *
+		 * @return BuddyPress
+		 */
+		function buddypress() {
+			return isset( $GLOBALS['bp'] ) ? $GLOBALS['bp'] : false;
+		}
+	}
+
+	// Bail if in maintenance mode
+	if ( ! buddypress() || buddypress()->maintenance_mode ) {
+		return;
+	}
+
+	// Include the BuddyPress Component
+	require_once bbpress()->includes_dir . 'extend/buddypress/loader.php';
+
+	// Instantiate BuddyPress for bbPress
+	bbpress()->extend->buddypress = new BBP_Forums_Component();
+}
Index: /tags/2.6.6/src/includes/core/filters.php
===================================================================
--- /tags/2.6.6/src/includes/core/filters.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/filters.php	(revision 7162)
@@ -0,0 +1,421 @@
+<?php
+
+/**
+ * bbPress Filters
+ *
+ * This file contains the filters that are used through-out bbPress. They are
+ * consolidated here to make searching for them easier, and to help developers
+ * understand at a glance the order in which things occur.
+ *
+ * There are a few common places that additional filters can currently be found
+ *
+ *  - bbPress: In {@link bbPress::setup_actions()} in bbpress.php
+ *  - Admin: More in {@link BBP_Admin::setup_actions()} in admin.php
+ *
+ * @package bbPress
+ * @subpackage Core
+ *
+ * @see /core/actions.php
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach bbPress to WordPress
+ *
+ * bbPress uses its own internal actions to help aid in third-party plugin
+ * development, and to limit the amount of potential future code changes when
+ * updates to WordPress core occur.
+ *
+ * These actions exist to create the concept of 'plugin dependencies'. They
+ * provide a safe way for plugins to execute code *only* when bbPress is
+ * installed and activated, without needing to do complicated guesswork.
+ *
+ * For more information on how this works, see the 'Plugin Dependency' section
+ * near the bottom of this file.
+ *
+ *           v--WordPress Actions       v--bbPress Sub-actions
+ */
+add_filter( 'request',                 'bbp_request',            10    );
+add_filter( 'template_include',        'bbp_template_include',   10    );
+add_filter( 'wp_mail',                 'bbp_mail',               10, 3 );
+add_filter( 'wp_title',                'bbp_title',              10, 3 );
+add_filter( 'body_class',              'bbp_body_class',         10, 2 );
+add_filter( 'map_meta_cap',            'bbp_map_meta_caps',      10, 4 );
+add_filter( 'allowed_themes',          'bbp_allowed_themes',     10    );
+add_filter( 'redirect_canonical',      'bbp_redirect_canonical', 10    );
+add_filter( 'login_redirect',          'bbp_redirect_login',     2,  3 );
+add_filter( 'logout_url',              'bbp_logout_url',         2,  2 );
+add_filter( 'plugin_locale',           'bbp_plugin_locale',      10, 2 );
+
+// Fix post author id for anonymous posts (set it back to 0) when the post status is changed
+add_filter( 'wp_insert_post_data', 'bbp_fix_post_author', 30, 2 );
+
+// Force comments_status on bbPress post types
+add_filter( 'comments_open', 'bbp_force_comment_status' );
+
+// Remove forums roles from list of all roles
+add_filter( 'editable_roles', 'bbp_filter_blog_editable_roles' );
+
+// Reply title fallback
+add_filter( 'the_title', 'bbp_get_reply_title_fallback', 2, 2 );
+
+// Avoid queries & 404s
+add_filter( 'pre_handle_404',  'bbp_pre_handle_404',  10, 2 );
+add_action( 'posts_pre_query', 'bbp_posts_pre_query', 10, 2 );
+
+// User Creation
+add_filter( 'signup_user_meta', 'bbp_user_add_role_to_signup_meta', 10 );
+
+/**
+ * Emails
+ *
+ * bbPress sends emails for a few different reasons, largely related to user
+ * notifications or account changes. Because the `wp_mail` filter can be a
+ * crowded space, the `bbp_mail` subfilter should be used in conjunction with
+ * bbp_get_email_header() to narrow the results to only bbPress emails.
+ */
+add_filter( 'bbp_mail', 'bbp_chunk_emails' );
+
+/**
+ * Feeds
+ *
+ * bbPress comes with a number of custom RSS2 feeds that get handled outside
+ * the normal scope of feeds that WordPress would normally serve. To do this,
+ * we filter every page request, listen for a feed request, and trap it.
+ */
+add_filter( 'bbp_request', 'bbp_request_feed_trap' );
+
+/**
+ * Template Compatibility
+ *
+ * If you want to completely bypass this and manage your own custom bbPress
+ * template hierarchy, start here by removing this filter, then look at how
+ * bbp_template_include() works and do something similar. :)
+ */
+add_filter( 'bbp_template_include', 'bbp_template_include_theme_supports', 2, 1 );
+add_filter( 'bbp_template_include', 'bbp_template_include_theme_compat',   4, 2 );
+
+// Filter bbPress template locations
+add_filter( 'bbp_get_template_stack', 'bbp_add_template_stack_locations' );
+
+// Links
+add_filter( 'paginate_links',          'bbp_add_view_all' );
+add_filter( 'bbp_get_topic_permalink', 'bbp_add_view_all' );
+add_filter( 'bbp_get_reply_permalink', 'bbp_add_view_all' );
+add_filter( 'bbp_get_forum_permalink', 'bbp_add_view_all' );
+
+// wp_filter_kses on new/edit forum/topic/reply title
+add_filter( 'bbp_new_forum_pre_title',  'wp_filter_kses' );
+add_filter( 'bbp_new_reply_pre_title',  'wp_filter_kses' );
+add_filter( 'bbp_new_topic_pre_title',  'wp_filter_kses' );
+add_filter( 'bbp_edit_forum_pre_title', 'wp_filter_kses' );
+add_filter( 'bbp_edit_reply_pre_title', 'wp_filter_kses' );
+add_filter( 'bbp_edit_topic_pre_title', 'wp_filter_kses' );
+
+// Prevent posting malicious or malformed content on new/edit topic/reply
+add_filter( 'bbp_new_reply_pre_content',  'bbp_encode_bad',  10 );
+add_filter( 'bbp_new_reply_pre_content',  'bbp_code_trick',  20 );
+add_filter( 'bbp_new_reply_pre_content',  'bbp_filter_kses', 30 );
+add_filter( 'bbp_new_reply_pre_content',  'balanceTags',     40 );
+add_filter( 'bbp_new_topic_pre_content',  'bbp_encode_bad',  10 );
+add_filter( 'bbp_new_topic_pre_content',  'bbp_code_trick',  20 );
+add_filter( 'bbp_new_topic_pre_content',  'bbp_filter_kses', 30 );
+add_filter( 'bbp_new_topic_pre_content',  'balanceTags',     40 );
+add_filter( 'bbp_new_forum_pre_content',  'bbp_encode_bad',  10 );
+add_filter( 'bbp_new_forum_pre_content',  'bbp_code_trick',  20 );
+add_filter( 'bbp_new_forum_pre_content',  'bbp_filter_kses', 30 );
+add_filter( 'bbp_new_forum_pre_content',  'balanceTags',     40 );
+add_filter( 'bbp_edit_reply_pre_content', 'bbp_encode_bad',  10 );
+add_filter( 'bbp_edit_reply_pre_content', 'bbp_code_trick',  20 );
+add_filter( 'bbp_edit_reply_pre_content', 'bbp_filter_kses', 30 );
+add_filter( 'bbp_edit_reply_pre_content', 'balanceTags',     40 );
+add_filter( 'bbp_edit_topic_pre_content', 'bbp_encode_bad',  10 );
+add_filter( 'bbp_edit_topic_pre_content', 'bbp_code_trick',  20 );
+add_filter( 'bbp_edit_topic_pre_content', 'bbp_filter_kses', 30 );
+add_filter( 'bbp_edit_topic_pre_content', 'balanceTags',     40 );
+add_filter( 'bbp_edit_forum_pre_content', 'bbp_encode_bad',  10 );
+add_filter( 'bbp_edit_forum_pre_content', 'bbp_code_trick',  20 );
+add_filter( 'bbp_edit_forum_pre_content', 'bbp_filter_kses', 30 );
+add_filter( 'bbp_edit_forum_pre_content', 'balanceTags',     40 );
+
+// No follow and wp_unslash on links
+add_filter( 'bbp_get_reply_author_link',      'bbp_rel_nofollow' );
+add_filter( 'bbp_get_reply_author_link',      'wp_unslash'       );
+add_filter( 'bbp_get_reply_to_link',          'bbp_rel_nofollow' );
+add_filter( 'bbp_get_reply_to_link',          'wp_unslash'       );
+add_filter( 'bbp_get_topic_author_link',      'bbp_rel_nofollow' );
+add_filter( 'bbp_get_topic_author_link',      'wp_unslash'       );
+add_filter( 'bbp_get_topic_reply_link',       'bbp_rel_nofollow' );
+add_filter( 'bbp_get_topic_reply_link',       'wp_unslash'       );
+add_filter( 'bbp_get_user_favorites_link',    'bbp_rel_nofollow' );
+add_filter( 'bbp_get_user_favorites_link',    'wp_unslash'       );
+add_filter( 'bbp_get_user_subscribe_link',    'bbp_rel_nofollow' );
+add_filter( 'bbp_get_user_subscribe_link',    'wp_unslash'       );
+add_filter( 'bbp_get_user_profile_link',      'bbp_rel_nofollow' );
+add_filter( 'bbp_get_user_profile_link',      'wp_unslash'       );
+add_filter( 'bbp_get_user_profile_edit_link', 'bbp_rel_nofollow' );
+add_filter( 'bbp_get_user_profile_edit_link', 'wp_unslash'       );
+add_filter( 'bbp_get_cancel_reply_to_link',   'bbp_rel_nofollow' );
+add_filter( 'bbp_get_cancel_reply_to_link',   'wp_unslash'       );
+
+// Run filters on reply content
+add_filter( 'bbp_get_reply_content', 'wptexturize',        6  );
+add_filter( 'bbp_get_reply_content', 'convert_chars',      8  );
+add_filter( 'bbp_get_reply_content', 'capital_P_dangit',   10 );
+add_filter( 'bbp_get_reply_content', 'convert_smilies',    20 );
+add_filter( 'bbp_get_reply_content', 'force_balance_tags', 30 );
+add_filter( 'bbp_get_reply_content', 'bbp_make_clickable', 40 );
+add_filter( 'bbp_get_reply_content', 'wpautop',            50 );
+add_filter( 'bbp_get_reply_content', 'bbp_rel_nofollow',   60 );
+
+// Run filters on topic content
+add_filter( 'bbp_get_topic_content', 'wptexturize',        6  );
+add_filter( 'bbp_get_topic_content', 'convert_chars',      8  );
+add_filter( 'bbp_get_topic_content', 'capital_P_dangit',   10 );
+add_filter( 'bbp_get_topic_content', 'convert_smilies',    20 );
+add_filter( 'bbp_get_topic_content', 'force_balance_tags', 30 );
+add_filter( 'bbp_get_topic_content', 'bbp_make_clickable', 40 );
+add_filter( 'bbp_get_topic_content', 'wpautop',            50 );
+add_filter( 'bbp_get_topic_content', 'bbp_rel_nofollow',   60 );
+
+// Admin-only
+if ( is_admin() ) {
+
+	// Run wp_kses_data on topic/reply content in admin section
+	add_filter( 'bbp_get_reply_content', 'bbp_kses_data' );
+	add_filter( 'bbp_get_topic_content', 'bbp_kses_data' );
+
+// Filters outside of wp-admin
+} else {
+
+	// WordPress 5.5.x and above
+	if ( function_exists( 'wp_filter_content_tags' ) ) {
+
+		// Responsive images
+		add_filter( 'bbp_get_reply_content', 'wp_filter_content_tags', 60 );
+		add_filter( 'bbp_get_topic_content', 'wp_filter_content_tags', 60 );
+
+	// WordPress 5.4.x and below
+	} else {
+
+		// Responsive images
+		add_filter( 'bbp_get_reply_content', 'wp_make_content_images_responsive', 60 );
+		add_filter( 'bbp_get_topic_content', 'wp_make_content_images_responsive', 60 );
+	}
+
+	// Revisions
+	add_filter( 'bbp_get_reply_content', 'bbp_reply_content_append_revisions',  99,  2 );
+	add_filter( 'bbp_get_topic_content', 'bbp_topic_content_append_revisions',  99,  2 );
+}
+
+// Form textarea output - undo the code-trick done pre-save, and sanitize
+add_filter( 'bbp_get_form_forum_content', 'bbp_code_trick_reverse' );
+add_filter( 'bbp_get_form_forum_content', 'esc_textarea'           );
+add_filter( 'bbp_get_form_forum_content', 'trim'                   );
+add_filter( 'bbp_get_form_topic_content', 'bbp_code_trick_reverse' );
+add_filter( 'bbp_get_form_topic_content', 'esc_textarea'           );
+add_filter( 'bbp_get_form_topic_content', 'trim'                   );
+add_filter( 'bbp_get_form_reply_content', 'bbp_code_trick_reverse' );
+add_filter( 'bbp_get_form_reply_content', 'esc_textarea'           );
+add_filter( 'bbp_get_form_reply_content', 'trim'                   );
+
+// Form input/output - sanitize
+add_filter( 'bbp_get_form_reply_edit_reason', 'esc_attr' );
+add_filter( 'bbp_get_form_reply_edit_reason', 'trim'     );
+add_filter( 'bbp_get_form_topic_edit_reason', 'esc_attr' );
+add_filter( 'bbp_get_form_topic_edit_reason', 'trim'     );
+add_filter( 'bbp_get_form_topic_title',       'esc_attr' );
+add_filter( 'bbp_get_form_topic_title',       'trim'     );
+add_filter( 'bbp_get_form_topic_tags',        'esc_attr' );
+add_filter( 'bbp_get_form_topic_tags',        'trim'     );
+add_filter( 'bbp_get_form_forum_type',        'esc_attr' );
+add_filter( 'bbp_get_form_forum_type',        'trim'     );
+add_filter( 'bbp_get_form_forum_visibility',  'esc_attr' );
+add_filter( 'bbp_get_form_forum_visibility',  'trim'     );
+add_filter( 'bbp_get_form_forum_moderators',  'esc_attr' );
+add_filter( 'bbp_get_form_forum_moderators',  'trim'     );
+add_filter( 'bbp_get_form_topic_forum',       'intval'   );
+add_filter( 'bbp_get_form_forum_parent',      'intval'   );
+add_filter( 'bbp_get_form_reply_to',          'intval'   );
+
+// Add number format filter to functions requesting formatted values
+add_filter( 'bbp_get_user_topic_count',         'bbp_number_format', 10 );
+add_filter( 'bbp_get_user_reply_count',         'bbp_number_format', 10 );
+add_filter( 'bbp_get_user_post_count',          'bbp_number_format', 10 );
+add_filter( 'bbp_get_forum_subforum_count',     'bbp_number_format', 10 );
+add_filter( 'bbp_get_forum_topic_count',        'bbp_number_format', 10 );
+add_filter( 'bbp_get_forum_reply_count',        'bbp_number_format', 10 );
+add_filter( 'bbp_get_forum_post_count',         'bbp_number_format', 10 );
+add_filter( 'bbp_get_topic_voice_count',        'bbp_number_format', 10 );
+add_filter( 'bbp_get_topic_reply_count',        'bbp_number_format', 10 );
+add_filter( 'bbp_get_topic_post_count',         'bbp_number_format', 10 );
+add_filter( 'bbp_get_topic_revision_count',     'bbp_number_format', 10 );
+add_filter( 'bbp_get_reply_revision_count',     'bbp_number_format', 10 );
+add_filter( 'bbp_get_forum_topic_count_hidden', 'bbp_number_format', 10 );
+add_filter( 'bbp_get_topic_reply_count_hidden', 'bbp_number_format', 10 );
+
+// Add number-not-negative filter to values that can never be negative numbers
+add_filter( 'bbp_get_user_topic_count',             'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_user_reply_count',             'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_user_post_count',              'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_subforum_count',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_topic_count',            'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_reply_count',            'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_post_count',             'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_voice_count',            'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_reply_count',            'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_post_count',             'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_topic_count_hidden',     'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_reply_count_hidden',     'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_revision_count',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_reply_revision_count',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_user_topic_count_int',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_user_reply_count_int',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_user_post_count_int',          'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_subforum_count_int',     'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_topic_count_int',        'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_reply_count_int',        'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_post_count_int',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_voice_count_int',        'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_reply_count_int',        'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_post_count_int',         'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_forum_topic_count_hidden_int', 'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_reply_count_hidden_int', 'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_topic_revision_count_int',     'bbp_number_not_negative', 8 );
+add_filter( 'bbp_get_reply_revision_count_int',     'bbp_number_not_negative', 8 );
+
+// Sanitize displayed user data
+add_filter( 'bbp_get_displayed_user_field', 'bbp_sanitize_displayed_user_field', 10, 3 );
+
+// Suppress private forum details
+add_filter( 'bbp_get_forum_topic_count',    'bbp_suppress_private_forum_meta',  10, 2 );
+add_filter( 'bbp_get_forum_reply_count',    'bbp_suppress_private_forum_meta',  10, 2 );
+add_filter( 'bbp_get_forum_post_count',     'bbp_suppress_private_forum_meta',  10, 2 );
+add_filter( 'bbp_get_forum_freshness_link', 'bbp_suppress_private_forum_meta',  10, 2 );
+add_filter( 'bbp_get_author_link',          'bbp_suppress_private_author_link', 10, 2 );
+add_filter( 'bbp_get_topic_author_link',    'bbp_suppress_private_author_link', 10, 2 );
+add_filter( 'bbp_get_reply_author_link',    'bbp_suppress_private_author_link', 10, 2 );
+
+// Allow private & hidden forum details for moderators
+add_filter( 'bbp_get_excluded_forum_ids', 'bbp_allow_forums_of_user', 10, 2 );
+
+// Topic and reply author display names
+add_filter( 'bbp_get_topic_author_display_name', 'wptexturize'   );
+add_filter( 'bbp_get_topic_author_display_name', 'convert_chars' );
+add_filter( 'bbp_get_topic_author_display_name', 'esc_html'      );
+add_filter( 'bbp_get_reply_author_display_name', 'wptexturize'   );
+add_filter( 'bbp_get_reply_author_display_name', 'convert_chars' );
+add_filter( 'bbp_get_reply_author_display_name', 'esc_html'      );
+
+/**
+ * Add filters to anonymous post author data
+ */
+// Post author name
+add_filter( 'bbp_pre_anonymous_post_author_name',    'trim',                10 );
+add_filter( 'bbp_pre_anonymous_post_author_name',    'sanitize_text_field', 10 );
+add_filter( 'bbp_pre_anonymous_post_author_name',    'wp_filter_kses',      10 );
+add_filter( 'bbp_pre_anonymous_post_author_name',    '_wp_specialchars',    30 );
+
+// Save email
+add_filter( 'bbp_pre_anonymous_post_author_email',   'trim',                10 );
+add_filter( 'bbp_pre_anonymous_post_author_email',   'sanitize_email',      10 );
+add_filter( 'bbp_pre_anonymous_post_author_email',   'wp_filter_kses',      10 );
+
+// Save URL
+add_filter( 'bbp_pre_anonymous_post_author_website', 'trim',                10 );
+add_filter( 'bbp_pre_anonymous_post_author_website', 'wp_strip_all_tags',   10 );
+add_filter( 'bbp_pre_anonymous_post_author_website', 'esc_url_raw',         10 );
+add_filter( 'bbp_pre_anonymous_post_author_website', 'wp_filter_kses',      10 );
+
+// Queries
+add_filter( 'posts_request', '_bbp_has_replies_where', 10, 2 );
+
+// Capabilities
+add_filter( 'bbp_map_meta_caps', 'bbp_map_primary_meta_caps',   10, 4 ); // Primary caps
+add_filter( 'bbp_map_meta_caps', 'bbp_map_forum_meta_caps',     10, 4 ); // Forums
+add_filter( 'bbp_map_meta_caps', 'bbp_map_topic_meta_caps',     10, 4 ); // Topics
+add_filter( 'bbp_map_meta_caps', 'bbp_map_topic_tag_meta_caps', 10, 4 ); // Topic tags
+add_filter( 'bbp_map_meta_caps', 'bbp_map_reply_meta_caps',     10, 4 ); // Replies
+
+// Clickables
+add_filter( 'bbp_make_clickable', 'bbp_make_urls_clickable',      2 ); // https://bbpress.org
+add_filter( 'bbp_make_clickable', 'bbp_make_ftps_clickable',      4 ); // ftps://bbpress.org
+add_filter( 'bbp_make_clickable', 'bbp_make_emails_clickable',    6 ); // jjj@bbpress.org
+add_filter( 'bbp_make_clickable', 'bbp_make_mentions_clickable',  8 ); // @jjj
+
+/** Deprecated ****************************************************************/
+
+/**
+ * The following filters are deprecated.
+ *
+ * These filters were most likely replaced by bbp_parse_args(), which includes
+ * both passive and aggressive filters anywhere parse_args is used to compare
+ * default arguments to passed arguments, without sprinkling the project with
+ * _before_ and _after_ filters everywhere.
+ */
+
+/**
+ * Deprecated locale filter
+ *
+ * @since 2.2.0 bbPress (r4213)
+ *
+ * @param string $locale
+ * @return string  $domain
+ */
+function _bbp_filter_locale( $locale = '', $domain = '' ) {
+
+	// Only apply to the bbPress text-domain
+	if ( bbpress()->domain !== $domain ) {
+		return $locale;
+	}
+
+	return apply_filters( 'bbpress_locale', $locale, $domain );
+}
+add_filter( 'bbp_plugin_locale', '_bbp_filter_locale', 10, 1 );
+
+/**
+ * Deprecated forums query filter
+ *
+ * @since 2.1.0 bbPress (r3961)
+ *
+ * @param array $args
+ * @return array
+ */
+function _bbp_has_forums_query( $args = array() ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_has_forums_query', $args );
+}
+add_filter( 'bbp_after_has_forums_parse_args', '_bbp_has_forums_query' );
+
+/**
+ * Deprecated topics query filter
+ *
+ * @since 2.1.0 bbPress (r3961)
+ *
+ * @param array $args
+ * @return array
+ */
+function _bbp_has_topics_query( $args = array() ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_has_topics_query', $args );
+}
+add_filter( 'bbp_after_has_topics_parse_args', '_bbp_has_topics_query' );
+
+/**
+ * Deprecated replies query filter
+ *
+ * @since 2.1.0 bbPress (r3961)
+ *
+ * @param array $args
+ * @return array
+ */
+function _bbp_has_replies_query( $args = array() ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_has_replies_query', $args );
+}
+add_filter( 'bbp_after_has_replies_parse_args', '_bbp_has_replies_query' );
Index: /tags/2.6.6/src/includes/core/functions.php
===================================================================
--- /tags/2.6.6/src/includes/core/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/functions.php	(revision 7162)
@@ -0,0 +1,657 @@
+<?php
+
+/**
+ * bbPress Core Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Versions ******************************************************************/
+
+/**
+ * Output the bbPress version
+ *
+ * @since 2.0.0 bbPress (r3468)
+ */
+function bbp_version() {
+	echo bbp_get_version();
+}
+	/**
+	 * Return the bbPress version
+	 *
+	 * @since 2.0.0 bbPress (r3468)
+	 *
+	 * @retrun string The bbPress version
+	 */
+	function bbp_get_version() {
+		return bbpress()->version;
+	}
+
+/**
+ * Output the bbPress database version
+ *
+ * @since 2.0.0 bbPress (r3468)
+ */
+function bbp_db_version() {
+	echo bbp_get_db_version();
+}
+	/**
+	 * Return the bbPress database version
+	 *
+	 * @since 2.0.0 bbPress (r3468)
+	 *
+	 * @retrun string The bbPress version
+	 */
+	function bbp_get_db_version() {
+		return bbpress()->db_version;
+	}
+
+/**
+ * Output the bbPress database version directly from the database
+ *
+ * @since 2.0.0 bbPress (r3468)
+ */
+function bbp_db_version_raw() {
+	echo bbp_get_db_version_raw();
+}
+	/**
+	 * Return the bbPress database version directly from the database
+	 *
+	 * @since 2.0.0 bbPress (r3468)
+	 *
+	 * @retrun string The current bbPress version
+	 */
+	function bbp_get_db_version_raw() {
+		return get_option( '_bbp_db_version', '' );
+	}
+
+/** Post Meta *****************************************************************/
+
+/**
+ * Update the forum meta ID of a post
+ *
+ * @since 2.0.0 bbPress (r3181)
+ *
+ * @param int $post_id  The post to update
+ * @param int $forum_id The forum
+ */
+function bbp_update_forum_id( $post_id = 0, $forum_id = 0 ) {
+
+	// Allow the forum ID to be updated 'just in time' before save
+	$forum_id = (int) apply_filters( 'bbp_update_forum_id', $forum_id, $post_id );
+
+	// Update the post meta forum ID
+	update_post_meta( $post_id, '_bbp_forum_id', $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Update the topic meta ID of a post
+ *
+ * @since 2.0.0 bbPress (r3181)
+ *
+ * @param int $post_id  The post to update
+ * @param int $topic_id The topic
+ */
+function bbp_update_topic_id( $post_id = 0, $topic_id = 0 ) {
+
+	// Allow the topic ID to be updated 'just in time' before save
+	$topic_id = (int) apply_filters( 'bbp_update_topic_id', $topic_id, $post_id );
+
+	// Update the post meta topic ID
+	update_post_meta( $post_id, '_bbp_topic_id', $topic_id );
+
+	return $topic_id;
+}
+
+/**
+ * Update the reply meta ID of a post
+ *
+ * @since 2.0.0 bbPress (r3181)
+ *
+ * @param int $post_id  The post to update
+ * @param int $reply_id The reply
+ */
+function bbp_update_reply_id( $post_id = 0, $reply_id = 0 ) {
+
+	// Allow the reply ID to be updated 'just in time' before save
+	$reply_id = (int) apply_filters( 'bbp_update_reply_id', $reply_id, $post_id );
+
+	// Update the post meta reply ID
+	update_post_meta( $post_id, '_bbp_reply_id', $reply_id );
+
+	return $reply_id;
+}
+
+/**
+ * Update the reply-to meta ID of a post
+ *
+ * @since 2.6.0 bbPress (r5735)
+ *
+ * @param int $post_id  The post to update
+ * @param int $reply_id The reply ID
+ */
+function bbp_update_reply_to_id( $post_id = 0, $reply_id = 0 ) {
+
+	// Allow the reply ID to be updated 'just in time' before save
+	$reply_id = (int) apply_filters( 'bbp_update_reply_to_id', $reply_id, $post_id );
+
+	// Update the post meta reply ID
+	update_post_meta( $post_id, '_bbp_reply_to', $reply_id );
+
+	return $reply_id;
+}
+
+/** Views *********************************************************************/
+
+/**
+ * Get the registered views
+ *
+ * Does nothing much other than return the {@link $bbp->views} variable
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @return array Views
+ */
+function bbp_get_views() {
+	return bbpress()->views;
+}
+
+/**
+ * Register a bbPress view
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view View name
+ * @param string $title View title
+ * @param mixed $query_args {@link bbp_has_topics()} arguments.
+ * @param bool $feed Have a feed for the view? Defaults to true.
+ * @param string $capability Capability that the current user must have
+ *
+ * @return array The just registered (but processed) view
+ */
+function bbp_register_view( $view, $title, $query_args = '', $feed = true, $capability = '' ) {
+
+	// Bail if user does not have capability
+	if ( ! empty( $capability ) && ! current_user_can( $capability ) ) {
+		return false;
+	}
+
+	$bbp   = bbpress();
+	$view  = sanitize_title( $view );
+	$title = esc_html( $title );
+
+	if ( empty( $view ) || empty( $title ) ) {
+		return false;
+	}
+
+	$query_args = bbp_parse_args( $query_args, '', 'register_view' );
+
+	// Set show_stickies to false if it wasn't supplied
+	if ( ! isset( $query_args['show_stickies'] ) ) {
+		$query_args['show_stickies'] = false;
+	}
+
+	$bbp->views[ $view ] = array(
+		'title'  => $title,
+		'query'  => $query_args,
+		'feed'   => $feed
+	);
+
+	return $bbp->views[ $view ];
+}
+
+/**
+ * Deregister a bbPress view
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view View name
+ * @return bool False if the view doesn't exist, true on success
+ */
+function bbp_deregister_view( $view ) {
+	$bbp  = bbpress();
+	$view = sanitize_title( $view );
+
+	if ( ! isset( $bbp->views[ $view ] ) ) {
+		return false;
+	}
+
+	unset( $bbp->views[ $view ] );
+
+	return true;
+}
+
+/**
+ * Run the query of a topic-view
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view Optional. View id
+ * @param mixed $new_args New arguments. See {@link bbp_has_topics()}
+ * @return bool False if the view doesn't exist, otherwise if topics are there
+ */
+function bbp_view_query( $view = '', $new_args = '' ) {
+
+	// Get view, or bail
+	$view = bbp_get_view_id( $view );
+	if ( empty( $view ) ) {
+		return false;
+	}
+
+	$query_args = bbp_get_view_query_args( $view );
+
+	if ( ! empty( $new_args ) ) {
+		$new_args   = bbp_parse_args( $new_args, '', 'view_query' );
+		$query_args = array_merge( $query_args, $new_args );
+	}
+
+	return bbp_has_topics( $query_args );
+}
+
+/**
+ * Return the query arguments of a topic-view
+ *
+ * @since 2.0.0 bbPress (r2789)
+ *
+ * @param string $view View name
+ * @return array Query arguments
+ */
+function bbp_get_view_query_args( $view = '' ) {
+	$bbp    = bbpress();
+	$view   = bbp_get_view_id( $view );
+	$retval = ! empty( $view ) && ! empty( $bbp->views[ $view ] )
+		? $bbp->views[ $view ]['query']
+		: array();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_view_query_args', $retval, $view );
+}
+
+/** Errors ********************************************************************/
+
+/**
+ * Adds an error message to later be output in the theme
+ *
+ * @since 2.0.0 bbPress (r3381)
+ *
+ * @see WP_Error()
+ *
+ * @param string $code Unique code for the error message
+ * @param string $message Translated error message
+ * @param string $data Any additional data passed with the error message
+ */
+function bbp_add_error( $code = '', $message = '', $data = '' ) {
+	bbpress()->errors->add( $code, $message, $data );
+}
+
+/**
+ * Check if error messages exist in queue
+ *
+ * @since 2.0.0 bbPress (r3381)
+ *
+ * @see WP_Error()
+ */
+function bbp_has_errors() {
+	$has_errors = bbpress()->errors->get_error_codes()
+		? true
+		: false;
+
+	return (bool) apply_filters( 'bbp_has_errors', $has_errors, bbpress()->errors );
+}
+
+/** Mentions ******************************************************************/
+
+/**
+ * Set the pattern used for matching usernames for mentions.
+ *
+ * Moved into its own function to allow filtering of the regex pattern
+ * anywhere mentions might be used.
+ *
+ * @since 2.4.0 bbPress (r4997)
+ * @deprecated 2.6.0 bbp_make_clickable()
+ *
+ * @return string Pattern to match usernames with
+ */
+function bbp_find_mentions_pattern() {
+
+	// Filter & return
+	return apply_filters( 'bbp_find_mentions_pattern', '/[@]+([A-Za-z0-9-_\.@]+)\b/' );
+}
+
+/**
+ * Searches through the content to locate usernames, designated by an @ sign.
+ *
+ * @since 2.2.0 bbPress (r4323)
+ * @deprecated 2.6.0 bbp_make_clickable()
+ *
+ * @param string $content The content
+ * @return bool|array $usernames Existing usernames. False if no matches.
+ */
+function bbp_find_mentions( $content = '' ) {
+	$pattern   = bbp_find_mentions_pattern();
+	preg_match_all( $pattern, $content, $usernames );
+	$usernames = array_unique( array_filter( $usernames[1] ) );
+
+	// Bail if no usernames
+	if ( empty( $usernames ) ) {
+		$usernames = false;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_find_mentions', $usernames, $pattern, $content );
+}
+
+/**
+ * Finds and links @-mentioned users in the content
+ *
+ * @since 2.2.0 bbPress (r4323)
+ * @deprecated 2.6.0 bbp_make_clickable()
+ *
+ * @return string $content Content filtered for mentions
+ */
+function bbp_mention_filter( $content = '' ) {
+
+	// Get Usernames and bail if none exist
+	$usernames = bbp_find_mentions( $content );
+	if ( empty( $usernames ) ) {
+		return $content;
+	}
+
+	// Loop through usernames and link to profiles
+	foreach ( (array) $usernames as $username ) {
+
+		// Skip if username does not exist or user is not active
+		$user = get_user_by( 'slug', $username );
+		if ( empty( $user->ID ) || bbp_is_user_inactive( $user->ID ) ) {
+			continue;
+		}
+
+		// Link
+		$profile_url  = bbp_get_user_profile_url( $user->ID );
+		$profile_link = sprintf( '<a href="%1$s">@%2$s</a>', esc_url( $profile_url ), esc_html( $username ) );
+		$no_followed  = bbp_rel_nofollow( $profile_link );
+		$pattern      = "/(@{$username}\b)/";
+
+		// Replace name in content
+		$content = preg_replace( $pattern, $no_followed, $content );
+	}
+
+	// Return modified content
+	return $content;
+}
+
+/** Post Statuses *************************************************************/
+
+/**
+ * Return the public post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_public_status_id() {
+	return bbpress()->public_status_id;
+}
+
+/**
+ * Return the pending post status ID
+ *
+ * @since 2.1.0 bbPress (r3581)
+ *
+ * @return string
+ */
+function bbp_get_pending_status_id() {
+	return bbpress()->pending_status_id;
+}
+
+/**
+ * Return the private post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_private_status_id() {
+	return bbpress()->private_status_id;
+}
+
+/**
+ * Return the hidden post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_hidden_status_id() {
+	return bbpress()->hidden_status_id;
+}
+
+/**
+ * Return the closed post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_closed_status_id() {
+	return bbpress()->closed_status_id;
+}
+
+/**
+ * Return the spam post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_spam_status_id() {
+	return bbpress()->spam_status_id;
+}
+
+/**
+ * Return the trash post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_trash_status_id() {
+	return bbpress()->trash_status_id;
+}
+
+/**
+ * Return the orphan post status ID
+ *
+ * @since 2.0.0 bbPress (r3504)
+ *
+ * @return string
+ */
+function bbp_get_orphan_status_id() {
+	return bbpress()->orphan_status_id;
+}
+
+/** Rewrite IDs ***************************************************************/
+
+/**
+ * Return the unique ID for user profile rewrite rules
+ *
+ * @since 2.1.0 bbPress (r3762)
+ *
+ * @return string
+ */
+function bbp_get_user_rewrite_id() {
+	return bbpress()->user_id;
+}
+
+/**
+ * Return the unique ID for all edit rewrite rules (forum|topic|reply|tag|user)
+ *
+ * @since 2.1.0 bbPress (r3762)
+ *
+ * @return string
+ */
+function bbp_get_edit_rewrite_id() {
+	return bbpress()->edit_id;
+}
+
+/**
+ * Return the unique ID for all search rewrite rules
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @return string
+ */
+function bbp_get_search_rewrite_id() {
+	return bbpress()->search_id;
+}
+
+/**
+ * Return the unique ID for user topics rewrite rules
+ *
+ * @since 2.2.0 bbPress (r4321)
+ *
+ * @return string
+ */
+function bbp_get_user_topics_rewrite_id() {
+	return bbpress()->tops_id;
+}
+
+/**
+ * Return the unique ID for user replies rewrite rules
+ *
+ * @since 2.2.0 bbPress (r4321)
+ *
+ * @return string
+ */
+function bbp_get_user_replies_rewrite_id() {
+	return bbpress()->reps_id;
+}
+
+/**
+ * Return the unique ID for user favorites rewrite rules
+ *
+ * @since 2.2.0 bbPress (r4181)
+ *
+ * @return string
+ */
+function bbp_get_user_favorites_rewrite_id() {
+	return bbpress()->favs_id;
+}
+
+/**
+ * Return the unique ID for user subscriptions rewrite rules
+ *
+ * @since 2.2.0 bbPress (r4181)
+ *
+ * @return string
+ */
+function bbp_get_user_subscriptions_rewrite_id() {
+	return bbpress()->subs_id;
+}
+
+/**
+ * Return the unique ID for user engagement rewrite rules
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @return string
+ */
+function bbp_get_user_engagements_rewrite_id() {
+	return bbpress()->engagements_id;
+}
+
+/**
+ * Return the unique ID for topic view rewrite rules
+ *
+ * @since 2.1.0 bbPress (r3762)
+ *
+ * @return string
+ */
+function bbp_get_view_rewrite_id() {
+	return bbpress()->view_id;
+}
+
+/** Rewrite Extras ************************************************************/
+
+/**
+ * Get the id used for paginated requests
+ *
+ * @since 2.4.0 bbPress (r4926)
+ *
+ * @return string
+ */
+function bbp_get_paged_rewrite_id() {
+	return bbpress()->paged_id;
+}
+
+/**
+ * Delete a blogs rewrite rules, so that they are automatically rebuilt on
+ * the subsequent page load.
+ *
+ * @since 2.2.0 bbPress (r4198)
+ */
+function bbp_delete_rewrite_rules() {
+	delete_option( 'rewrite_rules' );
+}
+
+/** Requests ******************************************************************/
+
+/**
+ * Return true|false if this is a POST request
+ *
+ * @since 2.3.0 bbPress (r4790)
+ *
+ * @return bool
+ */
+function bbp_is_post_request() {
+	return (bool) ( 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) );
+}
+
+/**
+ * Return true|false if this is a GET request
+ *
+ * @since 2.3.0 bbPress (r4790)
+ *
+ * @return bool
+ */
+function bbp_is_get_request() {
+	return (bool) ( 'GET' === strtoupper( $_SERVER['REQUEST_METHOD'] ) );
+}
+
+/** Redirection ***************************************************************/
+
+/**
+ * Perform a safe, local redirect somewhere inside the current site
+ *
+ * On some setups, passing the value of wp_get_referer() may result in an empty
+ * value for $location, which results in an error on redirection. If $location
+ * is empty, we can safely redirect back to the forum root. This might change
+ * in a future version, possibly to the site root.
+ *
+ * @since 2.6.0 bbPress (r5658)
+ *
+ * @see  bbp_redirect_to_field()
+ *
+ * @param string $location The URL to redirect the user to.
+ * @param int    $status   Optional. The numeric code to give in the redirect
+ *                         headers. Default: 302.
+ */
+function bbp_redirect( $location = '', $status = 302 ) {
+
+	// Prevent errors from empty $location
+	if ( empty( $location ) ) {
+		$location = bbp_get_forums_url();
+	}
+
+	// Setup the safe redirect
+	wp_safe_redirect( $location, $status );
+
+	// Exit so the redirect takes place immediately
+	exit();
+}
Index: /tags/2.6.6/src/includes/core/index.php
===================================================================
--- /tags/2.6.6/src/includes/core/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/core/options.php
===================================================================
--- /tags/2.6.6/src/includes/core/options.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/options.php	(revision 7162)
@@ -0,0 +1,1004 @@
+<?php
+
+/**
+ * bbPress Options
+ *
+ * @package bbPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Get the default site options and their values.
+ *
+ * These option
+ *
+ * @since 2.0.0 bbPress (r3421)
+ *
+ * @return array Filtered option names and values
+ */
+function bbp_get_default_options() {
+	static $options = null;
+
+	// Store these to avoid recreating them
+	if ( is_null( $options ) ) {
+
+		// Use the participant role for the default-default role
+		$role    = bbp_get_participant_role();
+		$options = array(
+
+			/** DB Version ********************************************************/
+
+			'_bbp_db_version'             => 0,         // Database version
+			'_bbp_db_pending_upgrades'    => array(),   // Database upgrades pending
+
+			/** Flags *************************************************************/
+
+			'_bbp_flag_initial_content'   => 1,         // Flag to indicate initial content was created
+
+			/** Features **********************************************************/
+
+			'_bbp_enable_favorites'       => 1,         // Enable favorites
+			'_bbp_enable_subscriptions'   => 1,         // Enable subscriptions
+			'_bbp_enable_engagements'     => 1,         // Enable engagements
+			'_bbp_allow_content_edit'     => 1,         // Allow content edit
+			'_bbp_allow_content_throttle' => 1,         // Allow content throttle
+			'_bbp_allow_revisions'        => 1,         // Allow revisions
+			'_bbp_allow_topic_tags'       => 1,         // Allow topic tagging
+			'_bbp_allow_search'           => 1,         // Allow forum-wide search
+			'_bbp_allow_forum_mods'       => 1,         // Allow per-forum moderation
+			'_bbp_allow_global_access'    => 1,         // Allow users from all sites to post
+			'_bbp_allow_anonymous'        => 0,         // Allow anonymous posting
+			'_bbp_allow_super_mods'       => 0,         // Allow mods to edit users
+			'_bbp_allow_threaded_replies' => 0,         // Allow threaded replies
+			'_bbp_use_wp_editor'          => 1,         // Use the WordPress editor if available
+			'_bbp_use_autoembed'          => 0,         // Use oEmbed in topics and replies
+
+			/** Settings **********************************************************/
+
+			'_bbp_default_role'           => $role,     // Default forums role
+			'_bbp_edit_lock'              => 5,         // Lock post editing after 5 minutes
+			'_bbp_throttle_time'          => 10,        // Throttle post time to 10 seconds
+			'_bbp_thread_replies_depth'   => 2,         // Thread replies depth
+			'_bbp_theme_package_id'       => 'default', // The ID for the current theme package
+			'_bbp_settings_integration'   => 'basic',   // How to integrate into wp-admin
+			'_bbp_engagements_strategy'   => 'meta',    // How to interact with engagements
+
+			/** Per Page **********************************************************/
+
+			'_bbp_topics_per_page'      => 15,          // Topics per page
+			'_bbp_replies_per_page'     => 15,          // Replies per page
+			'_bbp_forums_per_page'      => 50,          // Forums per page
+			'_bbp_topics_per_rss_page'  => 25,          // Topics per RSS page
+			'_bbp_replies_per_rss_page' => 25,          // Replies per RSS page
+
+			/** Page For **********************************************************/
+
+			'_bbp_page_for_forums'      => 0,           // Page for forums
+			'_bbp_page_for_topics'      => 0,           // Page for forums
+			'_bbp_page_for_login'       => 0,           // Page for login
+			'_bbp_page_for_register'    => 0,           // Page for register
+			'_bbp_page_for_lost_pass'   => 0,           // Page for lost-pass
+
+			/** Forum Root ********************************************************/
+
+			'_bbp_root_slug'            => 'forums',    // Forum archive slug
+			'_bbp_show_on_root'         => 'forums',    // What to show on root (forums|topics)
+			'_bbp_include_root'         => 1,           // Include forum-archive before single slugs
+
+			/** Single Slugs ******************************************************/
+
+			'_bbp_forum_slug'           => 'forum',     // Forum slug
+			'_bbp_topic_slug'           => 'topic',     // Topic slug
+			'_bbp_reply_slug'           => 'reply',     // Reply slug
+			'_bbp_topic_tag_slug'       => 'topic-tag', // Topic tag slug
+
+			/** User Slugs ********************************************************/
+
+			'_bbp_user_slug'            => 'users',         // User profile slug
+			'_bbp_user_engs_slug'       => 'engagements',   // User engagements slug
+			'_bbp_user_favs_slug'       => 'favorites',     // User favorites slug
+			'_bbp_user_subs_slug'       => 'subscriptions', // User subscriptions slug
+			'_bbp_topic_archive_slug'   => 'topics',        // Topic archive slug
+			'_bbp_reply_archive_slug'   => 'replies',       // Reply archive slug
+
+			/** Other Slugs *******************************************************/
+
+			'_bbp_view_slug'            => 'view',      // View slug
+			'_bbp_search_slug'          => 'search',    // Search slug
+
+			/** Topics ************************************************************/
+
+			'_bbp_title_max_length'     => 80,          // Title Max Length
+			'_bbp_super_sticky_topics'  => '',          // Super stickies
+
+			/** Forums ************************************************************/
+
+			'_bbp_private_forums'       => '',          // Private forums
+			'_bbp_hidden_forums'        => '',          // Hidden forums
+
+			/** BuddyPress ********************************************************/
+
+			'_bbp_enable_group_forums'  => 1,           // Enable BuddyPress Group Extension
+			'_bbp_group_forums_root_id' => 0,           // Group Forums parent forum id
+
+			/** Akismet ***********************************************************/
+
+			'_bbp_enable_akismet'       => 1,           // Users from all sites can post
+
+			/** Converter *********************************************************/
+
+			// Connection
+			'_bbp_converter_db_user'    => '',          // Database User
+			'_bbp_converter_db_pass'    => '',          // Database Password
+			'_bbp_converter_db_name'    => '',          // Database Name
+			'_bbp_converter_db_port'    => 3306,        // Database Port
+			'_bbp_converter_db_server'  => 'localhost', // Database Server/IP
+			'_bbp_converter_db_prefix'  => '',          // Database table prefix
+
+			// Options
+			'_bbp_converter_rows'          => 100,      // Number of rows to query
+			'_bbp_converter_delay_time'    => 2,        // Seconds to wait between queries
+			'_bbp_converter_step'          => false,    // Current converter step
+			'_bbp_converter_start'         => false,    // Step to start at
+			'_bbp_converter_convert_users' => false,    // Whether to convert users
+			'_bbp_converter_halt'          => false,    // Halt on errors
+			'_bbp_converter_platform'      => false,    // Which platform to use
+			'_bbp_converter_query'         => false     // Last query
+		);
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_default_options', $options );
+}
+
+/**
+ * Add default options
+ *
+ * Hooked to bbp_activate, it is only called once when bbPress is activated.
+ * This is non-destructive, so existing settings will not be overridden.
+ *
+ * @since 2.0.0 bbPress (r3421)
+ */
+function bbp_add_options() {
+
+	// Add default options
+	foreach ( bbp_get_default_options() as $key => $value ) {
+		add_option( $key, $value );
+	}
+
+	// Allow previously activated plugins to append their own options.
+	do_action( 'bbp_add_options' );
+}
+
+/**
+ * Delete default options
+ *
+ * Hooked to bbp_uninstall, it is only called once when bbPress is uninstalled.
+ * This is destructive, so existing settings will be destroyed.
+ *
+ * @since 2.0.0 bbPress (r3421)
+ */
+function bbp_delete_options() {
+
+	// Add default options
+	foreach ( array_keys( bbp_get_default_options() ) as $key ) {
+		delete_option( $key );
+	}
+
+	// Allow previously activated plugins to append their own options.
+	do_action( 'bbp_delete_options' );
+}
+
+/**
+ * Add filters to each bbPress option and allow them to be overloaded from
+ * inside the $bbp->options array.
+ *
+ * @since 2.0.0 bbPress (r3451)
+ */
+function bbp_setup_option_filters() {
+
+	// Add filters to each bbPress option
+	foreach ( array_keys( bbp_get_default_options() ) as $key ) {
+		add_filter( 'pre_option_'     . $key, 'bbp_filter_pre_get_option', 10, 2 );
+		add_filter( 'default_option_' . $key, 'bbp_filter_default_option', 10, 3 );
+	}
+
+	// Allow previously activated plugins to append their own options.
+	do_action( 'bbp_setup_option_filters' );
+}
+
+/**
+ * Filter pre options and maybe overloaded from the $bbp->options array.
+ *
+ * This function should not be called directly.
+ *
+ * @since 2.0.0 bbPress (r3451)
+ * @access private
+ *
+ * @param bool   $value  Default value false
+ * @param string $option Name of the option
+ *
+ * @return mixed false if not overloaded, mixed if set
+ */
+function bbp_filter_pre_get_option( $value = false, $option = '' ) {
+
+	// Check the options global for preset value
+	if ( isset( bbpress()->options[ $option ] ) ) {
+		$value = bbpress()->options[ $option ];
+	}
+
+	// Always return a value, even if false
+	return $value;
+}
+
+/**
+ * Filter default_options set them from inside the $bbp->options array.
+ *
+ * This function should not be called directly.
+ *
+ * @since 2.6.0 bbPress (r3451)
+ * @access private
+ *
+ * @param bool $value Optional. Default value false
+ * @return mixed false if not overloaded, mixed if set
+ */
+function bbp_filter_default_option( $default = false, $option = '', $passed_default = false ) {
+	$options = bbp_get_default_options();
+
+	// Maybe use the default value
+	if ( isset( $options[ $option ] ) ) {
+
+		// Try to use the passed default and fallback to assumed default
+		$default = ( true === $passed_default )
+			? $default
+			: $options[ $option ];
+	}
+
+	// Always return a value, even if false
+	return $default;
+}
+
+/**
+ * Loads & caches bbPress options if a persistent cache is not being used.
+ *
+ * @since 2.6.0
+ */
+function bbp_pre_load_options() {
+
+	// Bail if using object cache or installing
+	if ( wp_using_ext_object_cache() || wp_installing() ) {
+		return;
+	}
+
+	// Maybe intercept
+	$strategy  = apply_filters( 'bbp_pre_load_options_strategy', 'notoptions' );
+	$intercept = bbp_maybe_intercept( __FUNCTION__, $strategy );
+	if ( bbp_is_intercepted( $intercept ) ) {
+		return $intercept;
+	}
+
+	// Get variables
+	$bbp         = bbpress();
+	$bbp_options = bbp_get_default_options();
+	$all_options = wp_load_alloptions();
+	$not_options = (array) wp_cache_get( 'notoptions', 'options' );
+
+	// Loop through all bbPress options to maybe cache their non-existence
+	foreach ( $bbp_options as $option => $value ) {
+
+		// Skip if already saved to database
+		if ( isset( $all_options[ $option ] ) ) {
+			continue;
+
+		// Skip if overloaded
+		} elseif ( isset( $bbp->options[ $option ] ) ) {
+			continue;
+
+		// Skip if already in cache
+		} elseif ( wp_cache_get( $option, 'options' ) !== false ) {
+			continue;
+
+		// Needs caching to avoid database hit
+		} else {
+
+			// Store internally, for easier identification later
+			$bbp->not_options[ $option ] = $value;
+
+			// Cache to notoptions
+			if ( 'notoptions' === $strategy ) {
+				$not_options[ $option ] = true;
+				wp_cache_set( 'notoptions', $not_options, 'options' );
+
+			// Cache to option
+			} elseif ( 'option' === $strategy ) {
+				wp_cache_set( $option, $value, 'options' );
+			}
+		}
+	}
+}
+
+/** Active? *******************************************************************/
+
+/**
+ * Checks if favorites feature is enabled.
+ *
+ * @since 2.0.0 bbPress (r2658)
+ *
+ * @param bool $default Optional.Default value true
+ * @return bool Is favorites enabled or not
+ */
+function bbp_is_favorites_active( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_favorites_active', (bool) get_option( '_bbp_enable_favorites', $default ) );
+}
+
+/**
+ * Checks if subscription feature is enabled.
+ *
+ * @since 2.0.0 bbPress (r2658)
+ *
+ * @param bool $default Optional.Default value true
+ * @return bool Is subscription enabled or not
+ */
+function bbp_is_subscriptions_active( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_subscriptions_active', (bool) get_option( '_bbp_enable_subscriptions', $default ) );
+}
+
+/**
+ * Checks if engagements feature is enabled.
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param bool $default Optional.Default value true
+ * @return bool Is engagements enabled or not
+ */
+function bbp_is_engagements_active( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_engagements_active', (bool) get_option( '_bbp_enable_engagements', $default ) );
+}
+
+/**
+ * Is content editing available when posting new topics & replies?
+ *
+ * @since 2.6.0 bbPress (r6441)
+ *
+ * @param bool $default Optional. Default value false
+ * @return bool Is content editing allowed?
+ */
+function bbp_allow_content_edit( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_content_edit', (bool) get_option( '_bbp_allow_content_edit', $default ) );
+}
+
+/**
+ * Is content throttling engaged when posting new topics & replies?
+ *
+ * @since 2.6.0 bbPress (r6441)
+ *
+ * @param bool $default Optional. Default value false
+ * @return bool Is content throttling allowed?
+ */
+function bbp_allow_content_throttle( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_content_throttle', (bool) get_option( '_bbp_allow_content_throttle', $default ) );
+}
+
+/**
+ * Are topic tags allowed
+ *
+ * @since 2.2.0 bbPress (r4097)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Are tags allowed?
+ */
+function bbp_allow_topic_tags( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_topic_tags', (bool) get_option( '_bbp_allow_topic_tags', $default ) );
+}
+
+/**
+ * Are per-forum moderators allowed
+ *
+ * @since 2.6.0 bbPress (r5834)
+ *
+ * @param bool $default Optional. Default value true.
+ *
+ * @return bool Are per-forum moderators allowed?
+ */
+function bbp_allow_forum_mods( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_forum_mods', (bool) get_option( '_bbp_allow_forum_mods', $default ) );
+}
+
+/**
+ * Are moderators allowed to edit users
+ *
+ * @since 2.6.0 bbPress (r6562)
+ *
+ * @param bool $default Optional. Default value true.
+ *
+ * @return bool Are moderators allowed to edit users?
+ */
+function bbp_allow_super_mods( $default = 0 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_super_mods', (bool) get_option( '_bbp_allow_super_mods', $default ) );
+}
+
+/**
+ * Is forum-wide searching allowed
+ *
+ * @since 2.4.0 bbPress (r4970)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Is forum-wide searching allowed?
+ */
+function bbp_allow_search( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_search', (bool) get_option( '_bbp_allow_search', $default ) );
+}
+
+/**
+ * Are threaded replies allowed
+ *
+ * @since 2.4.0 bbPress (r4964)
+ *
+ * @param bool $default Optional. Default value false
+ * @return bool Are threaded replies allowed?
+ */
+function bbp_allow_threaded_replies( $default = 0 ) {
+
+	// Filter & return
+	return (bool) apply_filters( '_bbp_allow_threaded_replies', (bool) get_option( '_bbp_allow_threaded_replies', $default ) );
+}
+
+/**
+ * Maximum reply thread depth
+ *
+ * @since 2.4.0 bbPress (r4944)
+ *
+ * @param int $default Thread replies depth
+ * @return int Thread replies depth
+ */
+function bbp_thread_replies_depth( $default = 2 ) {
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_thread_replies_depth', (int) get_option( '_bbp_thread_replies_depth', $default ) );
+}
+
+/**
+ * Are topic and reply revisions allowed
+ *
+ * @since 2.0.0 bbPress (r3412)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Are revisions allowed?
+ */
+function bbp_allow_revisions( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_revisions', (bool) get_option( '_bbp_allow_revisions', $default ) );
+}
+
+/**
+ * Is the anonymous posting allowed?
+ *
+ * @since 2.0.0 bbPress (r2659)
+ *
+ * @param bool $default Optional. Default value
+ * @return bool Is anonymous posting allowed?
+ */
+function bbp_allow_anonymous( $default = 0 ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_allow_anonymous', (bool) get_option( '_bbp_allow_anonymous', $default ) );
+}
+
+/**
+ * Is this forum available to all users on all sites in this installation?
+ *
+ * @since 2.0.0 bbPress (r3378)
+ *
+ * @param bool $default Optional. Default value false
+ * @return bool Is global access allowed?
+ */
+function bbp_allow_global_access( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_allow_global_access', (bool) get_option( '_bbp_allow_global_access', $default ) );
+}
+
+/**
+ * Get the default forums role (issued to users on their first visit to the
+ * forums) by bbp_set_current_user_default_role() and repair tools
+ *
+ * @since 2.2.0 bbPress (r4294)
+ *
+ * @param string $default Optional. Default value empty
+ * @return string The default forums user role
+ */
+function bbp_get_default_role( $default = 'bbp_participant' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_default_role', get_option( '_bbp_default_role', $default ) );
+}
+
+/**
+ * Use the WordPress editor if available
+ *
+ * @since 2.0.0 bbPress (r3386)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Use WP editor?
+ */
+function bbp_use_wp_editor( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_use_wp_editor', (bool) get_option( '_bbp_use_wp_editor', $default ) );
+}
+
+/**
+ * Use WordPress's oEmbed API
+ *
+ * @since 2.1.0 bbPress (r3752)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Use oEmbed?
+ */
+function bbp_use_autoembed( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_use_autoembed', (bool) get_option( '_bbp_use_autoembed', $default ) );
+}
+
+/**
+ * Get the current theme package ID
+ *
+ * @since 2.1.0 bbPress (r3829)
+ *
+ * @param string $default Optional. Default value 'default'
+ * @return string ID of the theme-package
+ */
+function bbp_get_theme_package_id( $default = 'default' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_package_id', get_option( '_bbp_theme_package_id', $default ) );
+}
+
+/**
+ * Output the maximum length of a title
+ *
+ * @since 2.0.0 bbPress (r3246)
+ *
+ * @param bool $default Optional. Default value 80
+ */
+function bbp_title_max_length( $default = 80 ) {
+	echo bbp_get_title_max_length( $default );
+}
+	/**
+	 * Return the maximum length of a title
+	 *
+	 * @since 2.0.0 bbPress (r3246)
+	 *
+	 * @param bool $default Optional. Default value 80
+	 * @return int Is anonymous posting allowed?
+	 */
+	function bbp_get_title_max_length( $default = 80 ) {
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_title_max_length', (int) get_option( '_bbp_title_max_length', $default ) );
+	}
+
+/**
+ * Output the number of minutes a topic or reply can be edited after it's
+ * published. Used by `bbp_past_edit_lock()`.
+ *
+ * @since 2.6.0 bbPress (r6868)
+ *
+ * @param bool $default Optional. Default value 5
+ */
+function bbp_edit_lock( $default = 5 ) {
+	echo bbp_get_edit_lock( $default );
+}
+	/**
+	 * Return the number of minutes a topic or reply can be edited after it's
+	 * published. Used by `bbp_past_edit_lock()`.
+	 *
+	 * @since 2.6.0 bbPress (r6868)
+	 *
+	 * @param bool $default Optional. Default value 5
+	 * @return int Is anonymous posting allowed?
+	 */
+	function bbp_get_edit_lock( $default = 5 ) {
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_edit_lock', (int) get_option( '_bbp_edit_lock', $default ) );
+	}
+
+/**
+ * Output the group forums root parent forum id
+ *
+ * @since 2.1.0 bbPress (r3575)
+ *
+ * @param int $default Optional. Default value
+ */
+function bbp_group_forums_root_id( $default = 0 ) {
+	echo bbp_get_group_forums_root_id( $default );
+}
+	/**
+	 * Return the group forums root parent forum id
+	 *
+	 * @since 2.1.0 bbPress (r3575)
+	 *
+	 * @param bool $default Optional. Default value 0
+	 * @return int The post ID for the root forum
+	 */
+	function bbp_get_group_forums_root_id( $default = 0 ) {
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_group_forums_root_id', (int) get_option( '_bbp_group_forums_root_id', $default ) );
+	}
+
+/**
+ * Checks if BuddyPress Group Forums are enabled
+ *
+ * @since 2.1.0 bbPress (r3575)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Is group forums enabled or not
+ */
+function bbp_is_group_forums_active( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_group_forums_active', (bool) get_option( '_bbp_enable_group_forums', $default ) );
+}
+
+/**
+ * Checks if Akismet is enabled
+ *
+ * @since 2.1.0 bbPress (r3575)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool Is Akismet enabled or not
+ */
+function bbp_is_akismet_active( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_akismet_active', (bool) get_option( '_bbp_enable_akismet', $default ) );
+}
+
+/**
+ * Integrate settings into existing WordPress pages
+ *
+ * There are 3 possible modes:
+ * - 'basic'   Traditional admin integration
+ * - 'compact' One "bbPress" top-level admin menu
+ * - 'deep'    Deeply integrate with the WordPress admin interface
+ *
+ * @since 2.4.0 bbPress (r4932)
+ *
+ * @param bool $default Optional. Default value false
+ * @return string How to integrate settings
+ */
+function bbp_settings_integration( $default = 'basic' ) {
+
+	// Get the option value
+	$integration = get_option( '_bbp_settings_integration', $default );
+
+	// Back-compat for deep/basic (pre-2.6)
+	if ( is_numeric( $integration ) ) {
+		$integration = ( 1 === (int) $integration )
+			? 'deep'
+			: 'basic';
+	}
+
+	// Fallback to 'basic' if invalid
+	if ( ! in_array( $integration, array( 'basic', 'deep', 'compact' ), true ) ) {
+		$integration = 'basic';
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_settings_integration', $integration, $default );
+}
+
+/**
+ * How to interact with engagements
+ *
+ * There are 3 possible strategies:
+ * - 'meta' 2.6 and higher. Uses multiple postmeta keys.
+ * - 'user' Pre-2.6. Uses comma-separated string of IDs in usermeta.
+ * - 'term' Alternate. Uses taxonomy term relationships.
+ *
+ * @since 2.6.0 bbPress (r6875)
+ *
+ * @param bool $default Optional. Default value false
+ * @return string How to interact with engagements
+ */
+function bbp_engagements_strategy( $default = 'meta' ) {
+
+	// Get the option value
+	$integration = get_option( '_bbp_engagements_strategy', $default );
+
+	// Check that class exists, or fallback
+	$class_name  = 'BBP_User_Engagements_' . ucwords( $integration );
+
+	// Fallback to 'meta' if invalid
+	if ( ! class_exists( $class_name ) ) {
+		$integration = 'meta';
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_engagements_strategy', $integration, $default );
+}
+
+/** Slugs *********************************************************************/
+
+/**
+ * Return the root slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'forums'
+ * @return string
+ */
+function bbp_get_root_slug( $default = 'forums' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_root_slug', get_option( '_bbp_root_slug', $default ) );
+}
+
+/**
+ * Are we including the root slug in front of forum pages?
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param bool $default Optional. Default value true
+ * @return bool
+ */
+function bbp_include_root_slug( $default = 1 ) {
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_include_root_slug', (bool) get_option( '_bbp_include_root', $default ) );
+}
+
+/**
+ * What to show on root, forums or topics?
+ *
+ * @since 2.4.0 bbPress (r4932)
+ *
+ * @param string $default Optional. Default value 'forums'
+ * @return string
+ */
+function bbp_show_on_root( $default = 'forums' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_show_on_root', get_option( '_bbp_show_on_root', $default ) );
+}
+
+/**
+ * Maybe return the root slug, based on whether or not it's included in the url
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'forums'
+ * @return string
+ */
+function bbp_maybe_get_root_slug() {
+	$slug   = bbp_get_root_slug();
+	$retval = ( ! empty( $slug ) && bbp_include_root_slug() )
+		? trailingslashit( $slug )
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_maybe_get_root_slug', $retval );
+}
+
+/**
+ * Return the single forum slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'forum'
+ * @return string
+ */
+function bbp_get_forum_slug( $default = 'forum' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_forum_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_forum_slug', $default ) );
+}
+
+/**
+ * Return the topic archive slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'topics'
+ * @return string
+ */
+function bbp_get_topic_archive_slug( $default = 'topics' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_archive_slug', get_option( '_bbp_topic_archive_slug', $default ) );
+}
+
+/**
+ * Return the reply archive slug
+ *
+ * @since 2.4.0 bbPress (r4925)
+ *
+ * @param string $default Optional. Default value 'replies'
+ * @return string
+ */
+function bbp_get_reply_archive_slug( $default = 'replies' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_reply_archive_slug', get_option( '_bbp_reply_archive_slug', $default ) );
+}
+
+/**
+ * Return the single topic slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'topic'
+ * @return string
+ */
+function bbp_get_topic_slug( $default = 'topic' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_topic_slug', $default ) );
+}
+
+/**
+ * Return the topic-tag taxonomy slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'topic-tag'
+ * @return string
+ */
+function bbp_get_topic_tag_tax_slug( $default = 'topic-tag' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_tag_tax_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_topic_tag_slug', $default ) );
+}
+
+/**
+ * Return the single reply slug (used mostly for editing)
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'reply'
+ * @return string
+ */
+function bbp_get_reply_slug( $default = 'reply' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_reply_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_reply_slug', $default ) );
+}
+
+/**
+ * Return the single user slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'users'
+ * @return string
+ */
+function bbp_get_user_slug( $default = 'users' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_user_slug', $default ) );
+}
+
+/**
+ * Return the single user favorites slug
+ *
+ * @since 2.2.0 bbPress (r4187)
+ *
+ * @param string $default Optional. Default value 'favorites'
+ * @return string
+ */
+function bbp_get_user_favorites_slug( $default = 'favorites' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_favorites_slug', get_option( '_bbp_user_favs_slug', $default ) );
+}
+
+/**
+ * Return the single user subscriptions slug
+ *
+ * @since 2.2.0 bbPress (r4187)
+ *
+ * @param string $default Optional. Default value 'subscriptions'
+ * @return string
+ */
+function bbp_get_user_subscriptions_slug( $default = 'subscriptions' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_subscriptions_slug', get_option( '_bbp_user_subs_slug', $default ) );
+}
+
+/**
+ * Return the single user engagements slug
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param string $default Optional. Default value 'engagements'
+ * @return string
+ */
+function bbp_get_user_engagements_slug( $default = 'engagements' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_engagements_slug', get_option( '_bbp_user_engs_slug', $default ) );
+}
+
+/**
+ * Return the topic view slug
+ *
+ * @since 2.1.0 bbPress (r3759)
+ *
+ * @param string $default Optional. Default value 'view'
+ * @return string
+ */
+function bbp_get_view_slug( $default = 'view' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_view_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_view_slug', $default ) );
+}
+
+/**
+ * Return the search slug
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @param string $default Optional. Default value 'search'
+ * @return string
+ */
+function bbp_get_search_slug( $default = 'search' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_search_slug', bbp_maybe_get_root_slug() . get_option( '_bbp_search_slug', $default ) );
+}
+
+/**
+ * Return the edit slug
+ *
+ * @since 2.6.2 bbPress (r6965)
+ *
+ * @param string $default Optional. Default value 'edit'
+ * @return string
+ */
+function bbp_get_edit_slug( $default = 'edit' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_edit_slug', get_option( '_bbp_edit_slug', $default ) );
+}
+
+/** Legacy ********************************************************************/
+
+/**
+ * Checks if there is a previous BuddyPress Forum configuration
+ *
+ * @since 2.1.0 bbPress (r3790)
+ *
+ * @param string $default Optional. Default empty string
+ * @return string The location of the bb-config.php file, if any
+ */
+function bbp_get_config_location( $default = '' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_config_location', get_option( 'bb-config-location', $default ) );
+}
Index: /tags/2.6.6/src/includes/core/sub-actions.php
===================================================================
--- /tags/2.6.6/src/includes/core/sub-actions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/sub-actions.php	(revision 7162)
@@ -0,0 +1,553 @@
+<?php
+
+/**
+ * Plugin Dependency
+ *
+ * The purpose of the following hooks is to mimic the behavior of something
+ * called 'plugin dependency' which enables a plugin to have plugins of their
+ * own in a safe and reliable way.
+ *
+ * We do this in bbPress by mirroring existing WordPress hooks in many places
+ * allowing dependant plugins to hook into the bbPress specific ones, thus
+ * guaranteeing proper code execution only when bbPress is active.
+ *
+ * The following functions are wrappers for hooks, allowing them to be
+ * manually called and/or piggy-backed on top of other hooks if needed.
+ *
+ * @package bbPress
+ * @subpackage Core
+ *
+ * @todo use anonymous functions when PHP minimum requirement allows (5.3)
+ */
+
+/** Activation Actions ********************************************************/
+
+/**
+ * Runs on bbPress activation
+ *
+ * @since 2.0.0 bbPress (r2509)
+ */
+function bbp_activation() {
+	do_action( 'bbp_activation' );
+}
+
+/**
+ * Runs on bbPress deactivation
+ *
+ * @since 2.0.0 bbPress (r2509)
+ */
+function bbp_deactivation() {
+	do_action( 'bbp_deactivation' );
+}
+
+/**
+ * Runs when uninstalling bbPress
+ *
+ * @since 2.0.0 bbPress (r2509)
+ */
+function bbp_uninstall() {
+	do_action( 'bbp_uninstall' );
+}
+
+/** Main Actions **************************************************************/
+
+/**
+ * Main action responsible for constants, globals, and includes
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_loaded() {
+	do_action( 'bbp_loaded' );
+}
+
+/**
+ * Setup constants
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_constants() {
+	do_action( 'bbp_constants' );
+}
+
+/**
+ * Setup globals BEFORE includes
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_boot_strap_globals() {
+	do_action( 'bbp_boot_strap_globals' );
+}
+
+/**
+ * Include files
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_includes() {
+	do_action( 'bbp_includes' );
+}
+
+/**
+ * Setup globals AFTER includes
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_setup_globals() {
+	do_action( 'bbp_setup_globals' );
+}
+
+/**
+ * Register any objects before anything is initialized
+ *
+ * @since 2.2.0 bbPress (r4180)
+ */
+function bbp_register() {
+	do_action( 'bbp_register' );
+}
+
+/**
+ * Initialize any code after everything has been loaded
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_init() {
+	do_action( 'bbp_init' );
+}
+
+/**
+ * Initialize roles
+ *
+ * @since 2.6.0 bbPress (r6106)
+ *
+ * @param WP_Roles $wp_roles The array of WP_Role objects that was initialized
+ */
+function bbp_roles_init( $wp_roles ) {
+	do_action( 'bbp_roles_init', $wp_roles );
+}
+
+/**
+ * Initialize widgets
+ *
+ * @since 2.0.0 bbPress (r3389)
+ */
+function bbp_widgets_init() {
+	do_action( 'bbp_widgets_init' );
+}
+
+/**
+ * Setup the currently logged-in user
+ *
+ * @link https://bbpress.trac.wordpress.org/ticket/2309
+ * @link https://core.trac.wordpress.org/ticket/24169
+ *
+ * @since 2.0.0 bbPress (r2695)
+ */
+function bbp_setup_current_user() {
+	do_action( 'bbp_setup_current_user' );
+}
+
+/**
+ * Setup the user engagements strategy
+ *
+ * @since 2.6.0 bbPress (r6875)
+ */
+function bbp_setup_engagements() {
+	do_action( 'bbp_setup_engagements' );
+}
+
+/** Supplemental Actions ******************************************************/
+
+/**
+ * Load translations for current language
+ *
+ * @since 2.0.0 bbPress (r2599)
+ */
+function bbp_load_textdomain() {
+	do_action( 'bbp_load_textdomain' );
+}
+
+/**
+ * Setup the post types
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_register_post_types() {
+	do_action( 'bbp_register_post_types' );
+}
+
+/**
+ * Setup the post statuses
+ *
+ * @since 2.0.0 bbPress (r2727)
+ */
+function bbp_register_post_statuses() {
+	do_action( 'bbp_register_post_statuses' );
+}
+
+/**
+ * Register the built in bbPress taxonomies
+ *
+ * @since 2.0.0 bbPress (r2464)
+ */
+function bbp_register_taxonomies() {
+	do_action( 'bbp_register_taxonomies' );
+}
+
+/**
+ * Register the default bbPress views
+ *
+ * @since 2.0.0 bbPress (r2789)
+ */
+function bbp_register_views() {
+	do_action( 'bbp_register_views' );
+}
+
+/**
+ * Register the default bbPress shortcodes
+ *
+ * @since 2.2.0 bbPress (r4211)
+ */
+function bbp_register_shortcodes() {
+	do_action( 'bbp_register_shortcodes' );
+}
+
+/**
+ * Register the default bbPress meta-data
+ *
+ * @since 2.6.0 bbPress (r46300)
+ */
+function bbp_register_meta() {
+	do_action( 'bbp_register_meta' );
+}
+
+/**
+ * Enqueue bbPress specific CSS and JS
+ *
+ * @since 2.0.0 bbPress (r3373)
+ */
+function bbp_enqueue_scripts() {
+	do_action( 'bbp_enqueue_scripts' );
+}
+
+/**
+ * Add the bbPress-specific rewrite tags
+ *
+ * @since 2.0.0 bbPress (r2753)
+ */
+function bbp_add_rewrite_tags() {
+	do_action( 'bbp_add_rewrite_tags' );
+}
+
+/**
+ * Add the bbPress-specific rewrite rules
+ *
+ * @since 2.4.0 bbPress (r4918)
+ */
+function bbp_add_rewrite_rules() {
+	do_action( 'bbp_add_rewrite_rules' );
+}
+
+/**
+ * Add the bbPress-specific permalink structures
+ *
+ * @since 2.4.0 bbPress (r4918)
+ */
+function bbp_add_permastructs() {
+	do_action( 'bbp_add_permastructs' );
+}
+
+/**
+ * Add the bbPress-specific login forum action
+ *
+ * @since 2.0.0 bbPress (r2753)
+ */
+function bbp_login_form_login() {
+	do_action( 'bbp_login_form_login' );
+}
+
+/**
+ * Add the bbPress-specific post status transition action
+ *
+ * @since 2.6.0 bbPress (r6792)
+ *
+ * @param string  $new_status New post status
+ * @param string  $old_status Old post status
+ * @param WP_Post $post       Post object
+ */
+function bbp_transition_post_status( $new_status = '', $old_status = '', $post = false ) {
+
+	// Get bbPress post types
+	$post_type = get_post_type( $post );
+	$types     = bbp_get_post_types();
+
+	// Bail if post is not a bbPress post type
+	if ( ! in_array( $post_type, $types, true ) ) {
+		return;
+	}
+
+	// Do the action
+	do_action( 'bbp_transition_post_status', $new_status, $old_status, $post );
+}
+
+/** User Actions **************************************************************/
+
+/**
+ * The main action for hooking into when a user account is updated
+ *
+ * @since 2.2.0 bbPress (r4304)
+ *
+ * @param int $user_id ID of user being edited
+ * @param array $old_user_data The old, unmodified user data
+ */
+function bbp_profile_update( $user_id = 0, $old_user_data = array() ) {
+	do_action( 'bbp_profile_update', $user_id, $old_user_data );
+}
+
+/**
+ * The main action for hooking into a user being registered
+ *
+ * @since 2.2.0 bbPress (r4304)
+ *
+ * @param int $user_id ID of user being edited
+ */
+function bbp_user_register( $user_id = 0 ) {
+	do_action( 'bbp_user_register', $user_id );
+}
+
+/** Final Action **************************************************************/
+
+/**
+ * bbPress has loaded and initialized everything, and is okay to go
+ *
+ * @since 2.0.0 bbPress (r2618)
+ */
+function bbp_ready() {
+	do_action( 'bbp_ready' );
+}
+
+/** Theme Permissions *********************************************************/
+
+/**
+ * The main action used for redirecting bbPress theme actions that are not
+ * permitted by the current_user
+ *
+ * @since 2.1.0 bbPress (r3605)
+ */
+function bbp_template_redirect() {
+	do_action( 'bbp_template_redirect' );
+}
+
+/** Theme Helpers *************************************************************/
+
+/**
+ * The main action used for executing code before the theme has been setup
+ *
+ * @since 2.1.0 bbPress (r3829)
+ */
+function bbp_register_theme_packages() {
+	do_action( 'bbp_register_theme_packages' );
+}
+
+/**
+ * The main action used for executing code before the theme has been setup
+ *
+ * @since 2.1.0 bbPress (r3732)
+ */
+function bbp_setup_theme() {
+	do_action( 'bbp_setup_theme' );
+}
+
+/**
+ * The main action used for executing code after the theme has been setup
+ *
+ * @since 2.1.0 bbPress (r3732)
+ */
+function bbp_after_setup_theme() {
+	do_action( 'bbp_after_setup_theme' );
+}
+
+/**
+ * The main action used for handling theme-side POST requests
+ *
+ * @since 2.3.0 bbPress (r4550)
+ */
+function bbp_post_request() {
+
+	// Bail if not a POST action
+	if ( ! bbp_is_post_request() ) {
+		return;
+	}
+
+	// Bail if no action, or if not a string (arrays not supported)
+	if ( empty( $_POST['action'] ) || ! is_string( $_POST['action'] ) ) {
+		return;
+	}
+
+	// Sanitize the POST action
+	$action = sanitize_key( $_POST['action'] );
+
+	// Bail if action was totally invalid
+	if ( empty( $action ) ) {
+		return;
+	}
+
+	// This dynamic action is probably the one you want to use. It narrows down
+	// the scope of the 'action' without needing to check it in your function.
+	do_action( 'bbp_post_request_' . $action );
+
+	// Use this static action if you don't mind checking the 'action' yourself.
+	do_action( 'bbp_post_request',   $action );
+}
+
+/**
+ * The main action used for handling theme-side GET requests
+ *
+ * @since 2.3.0 bbPress (r4550)
+ */
+function bbp_get_request() {
+
+	// Bail if not a POST action
+	if ( ! bbp_is_get_request() ) {
+		return;
+	}
+
+	// Bail if no action, or if not a string (arrays not supported)
+	if ( empty( $_GET['action'] ) || ! is_string( $_GET['action'] ) ) {
+		return;
+	}
+
+	// Sanitize the GET action
+	$action = sanitize_key( $_GET['action'] );
+
+	// Bail if action was totally invalid
+	if ( empty( $action ) ) {
+		return;
+	}
+
+	// This dynamic action is probably the one you want to use. It narrows down
+	// the scope of the 'action' without needing to check it in your function.
+	do_action( 'bbp_get_request_' . $action );
+
+	// Use this static action if you don't mind checking the 'action' yourself.
+	do_action( 'bbp_get_request',   $action );
+}
+
+/** Filters *******************************************************************/
+
+/**
+ * Filter the plugin locale and domain.
+ *
+ * @since 2.2.0 bbPress (r4213)
+ *
+ * @param string $locale
+ * @param string $domain
+ */
+function bbp_plugin_locale( $locale = '', $domain = '' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_plugin_locale', $locale, $domain );
+}
+
+/**
+ * Piggy back filter for WordPress's 'request' filter
+ *
+ * @since 2.1.0 bbPress (r3758)
+ *
+ * @param array $query_vars
+ * @return array
+ */
+function bbp_request( $query_vars = array() ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_request', $query_vars );
+}
+
+/**
+ * The main filter used for theme compatibility and displaying custom bbPress
+ * theme files.
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @param string $template
+ * @return string Template file to use
+ */
+function bbp_template_include( $template = '' ) {
+
+	// Filter & return
+	return apply_filters( 'bbp_template_include', $template );
+}
+
+/**
+ * Generate bbPress-specific rewrite rules
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @deprecated 2.4.0 bbPress (r4918)
+ *
+ * @param WP_Rewrite $wp_rewrite
+ */
+function bbp_generate_rewrite_rules( $wp_rewrite ) {
+	do_action_ref_array( 'bbp_generate_rewrite_rules', array( &$wp_rewrite ) );
+}
+
+/**
+ * Filter the allowed themes list for bbPress specific themes
+ *
+ * @since 2.0.0 bbPress (r2944)
+ *
+ * @param array $themes
+ *
+ * @return array Array of allowed themes
+ */
+function bbp_allowed_themes( $themes ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_allowed_themes', $themes );
+}
+
+/**
+ * Maps forum/topic/reply caps to built in WordPress caps
+ *
+ * @since 2.0.0 bbPress (r2593)
+ *
+ * @param array $caps Capabilities for meta capability
+ * @param string $cap Capability name
+ * @param int $user_id User id
+ * @param array $args Arguments
+ *
+ * @return array Array of capabilities
+ */
+function bbp_map_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_meta_caps', $caps, $cap, $user_id, $args );
+}
+
+/**
+ * Filter the arguments used by wp_mail for bbPress specific emails
+ *
+ * @since 2.6.0 bbPress (r6918)
+ *
+ * @param array $args A compacted array of wp_mail() arguments, including the "to" email,
+ *                    subject, message, headers, and attachments values.
+ *
+ * @return array Array of capabilities
+ */
+function bbp_mail( $args = array() ) {
+
+	// Bail if headers are missing/malformed
+	if ( empty( $args['headers'] ) || ! is_array( $args['headers'] ) ) {
+		return $args;
+	}
+
+	// Header to search all headers for
+	$bbp_header = bbp_get_email_header();
+
+	// Bail if no bbPress header found
+	if ( false === array_search( $bbp_header, $args['headers'], true ) ) {
+		return $args;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_mail', $args );
+}
Index: /tags/2.6.6/src/includes/core/template-functions.php
===================================================================
--- /tags/2.6.6/src/includes/core/template-functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/template-functions.php	(revision 7162)
@@ -0,0 +1,738 @@
+<?php
+
+/**
+ * bbPress Template Functions
+ *
+ * This file contains functions necessary to mirror the WordPress core template
+ * loading process. Many of those functions are not filterable, and even then
+ * would not be robust enough to predict where bbPress templates might exist.
+ *
+ * @package bbPress
+ * @subpackage TemplateFunctions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adds bbPress theme support to any active WordPress theme
+ *
+ * @since 2.0.0 bbPress (r3032)
+ *
+ * @param string $slug
+ * @param string $name Optional. Default null
+ */
+function bbp_get_template_part( $slug, $name = null ) {
+
+	// Execute code for this part
+	do_action( 'get_template_part_' . $slug, $slug, $name );
+
+	// Setup possible parts
+	$templates = array();
+	if ( isset( $name ) ) {
+		$templates[] = $slug . '-' . $name . '.php';
+	}
+	$templates[] = $slug . '.php';
+
+	// Allow template parst to be filtered
+	$templates = apply_filters( 'bbp_get_template_part', $templates, $slug, $name );
+
+	// Return the part that is found
+	return bbp_locate_template( $templates, true, false );
+}
+
+/**
+ * Retrieve the name of the highest priority template file that exists.
+ *
+ * Searches in the child theme before parent theme so that themes which
+ * inherit from a parent theme can just overload one file. If the template is
+ * not found in either of those, it looks in the theme-compat folder last.
+ *
+ * @since 2.1.0 bbPress (r3618)
+ *
+ * @param string|array $template_names Template file(s) to search for, in order.
+ * @param bool $load If true the template file will be loaded if it is found.
+ * @param bool $require_once Whether to require_once or require. Default true.
+ *                            Has no effect if $load is false.
+ * @return string The template filename if one is located.
+ */
+function bbp_locate_template( $template_names, $load = false, $require_once = true ) {
+
+	// No file found yet
+	$located            = false;
+	$template_locations = bbp_get_template_stack();
+
+	// Try to find a template file
+	foreach ( (array) $template_names as $template_name ) {
+
+		// Continue if template is empty
+		if ( empty( $template_name ) ) {
+			continue;
+		}
+
+		// Trim off any slashes from the template name
+		$template_name  = ltrim( $template_name, '/' );
+
+		// Loop through template stack
+		foreach ( (array) $template_locations as $template_location ) {
+
+			// Continue if $template_location is empty
+			if ( empty( $template_location ) ) {
+				continue;
+			}
+
+			// Check child theme first
+			if ( file_exists( trailingslashit( $template_location ) . $template_name ) ) {
+				$located = trailingslashit( $template_location ) . $template_name;
+				break 2;
+			}
+		}
+	}
+
+	/**
+	 * This action exists only to follow the standard bbPress coding convention,
+	 * and should not be used to short-circuit any part of the template locator.
+	 *
+	 * If you want to override a specific template part, please either filter
+	 * 'bbp_get_template_part' or add a new location to the template stack.
+	 */
+	do_action( 'bbp_locate_template', $located, $template_name, $template_names, $template_locations, $load, $require_once );
+
+	// Maybe load the template if one was located
+	if ( ( defined( 'WP_USE_THEMES' ) && WP_USE_THEMES ) && ( true === $load ) && ! empty( $located ) ) {
+		load_template( $located, $require_once );
+	}
+
+	return $located;
+}
+
+/**
+ * Locate an enqueueable file on the server. Used before being enqueued.
+ *
+ * If SCRIPT_DEBUG is set and the file includes a .min suffix, this function
+ * will automatically attempt to locate a non-minified version of that file.
+ *
+ * If SCRIPT_DEBUG is not set and the file exclude a .min suffix, this function
+ * will automatically attempt to locate a minified version of that file.
+ *
+ * See: https://bbpress.trac.wordpress.org/ticket/3218
+ *
+ * @since 2.6.0
+ *
+ * @param string $file
+ *
+ * @return boolean
+ */
+function bbp_locate_enqueueable( $file = '' ) {
+
+	// Bail if no file to locate
+	if ( empty( $file ) ) {
+		return false;
+	}
+
+	// Add file to files array
+	$files = array( $file );
+
+	// Get the file variant (minified or not, but opposite of $file)
+	$file_is_min  = ( false !== strpos( $file, '.min' ) );
+	$file_variant = ( false === $file_is_min )
+		? str_replace( array( '.css', '.js' ), array( '.min.css', '.min.js' ), $file )
+		: str_replace( '.min', '', $file );
+
+	// Are we debugging?
+	$script_debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
+
+	// Debugging, so prefer unminified files
+	if ( true === $script_debug ) {
+		if ( true === $file_is_min ) {
+			array_unshift( $files, $file_variant );
+		} else {
+			array_push( $files, $file_variant );
+		}
+
+	// Not debugging, so prefer minified files
+	} elseif ( false === $script_debug ) {
+		if ( true === $file_is_min ) {
+			array_push( $files, $file_variant );
+		} else {
+			array_unshift( $files, $file_variant );
+		}
+	}
+
+	// Return first found file location in the stack
+	return bbp_locate_template( $files, false, false );
+}
+
+/**
+ * Convert an enqueueable file path to a URL
+ *
+ * @since 2.6.0
+ * @param string $file
+ *
+ * @return string
+ */
+function bbp_urlize_enqueueable( $file = '' ) {
+
+	// Get DIR and URL
+	$content_dir = constant( 'WP_CONTENT_DIR' );
+	$content_url = content_url();
+
+	// IIS (Windows) here
+	// Replace back slashes with forward slash
+	if ( false !== strpos( $file, '\\' ) ) {
+		$file        = str_replace( '\\', '/', $file        );
+		$content_dir = str_replace( '\\', '/', $content_dir );
+	}
+
+	// Return path to file relative to site URL
+	return str_replace( $content_dir, $content_url, $file );
+}
+
+/**
+ * Enqueue a script from the highest priority location in the template stack.
+ *
+ * Registers the style if file provided (does NOT overwrite) and enqueues.
+ *
+ * @since 2.5.0 bbPress (r5180)
+ *
+ * @param string      $handle Name of the stylesheet.
+ * @param string|bool $file   Relative path to stylesheet. Example: '/css/mystyle.css'.
+ * @param array       $deps   An array of registered style handles this stylesheet depends on. Default empty array.
+ * @param string|bool $ver    String specifying the stylesheet version number, if it has one. This parameter is used
+ *                            to ensure that the correct version is sent to the client regardless of caching, and so
+ *                            should be included if a version number is available and makes sense for the stylesheet.
+ * @param string      $media  Optional. The media for which this stylesheet has been defined.
+ *                            Default 'all'. Accepts 'all', 'aural', 'braille', 'handheld', 'projection', 'print',
+ *                            'screen', 'tty', or 'tv'.
+ *
+ * @return mixed The style filename if one is located. False if not.
+ */
+function bbp_enqueue_style( $handle = '', $file = '', $deps = array(), $ver = false, $media = 'all' ) {
+
+	// Attempt to locate an enqueueable
+	$located = bbp_locate_enqueueable( $file );
+
+	// Enqueue if located
+	if ( ! empty( $located ) ) {
+
+		// Make sure there is always a version
+		if ( empty( $ver ) ) {
+			$ver = bbp_get_version();
+		}
+
+		// Make path to file relative to site URL
+		$located = bbp_urlize_enqueueable( $located );
+
+		// Register the style
+		wp_register_style( $handle, $located, $deps, $ver, $media );
+
+		// Enqueue the style
+		wp_enqueue_style( $handle );
+	}
+
+	return $located;
+}
+
+/**
+ * Enqueue a script from the highest priority location in the template stack.
+ *
+ * Registers the style if file provided (does NOT overwrite) and enqueues.
+ *
+ * @since 2.5.0 bbPress (r5180)
+ *
+ * @param string      $handle    Name of the script.
+ * @param string|bool $file      Relative path to the script. Example: '/js/myscript.js'.
+ * @param array       $deps      An array of registered handles this script depends on. Default empty array.
+ * @param string|bool $ver       Optional. String specifying the script version number, if it has one. This parameter
+ *                               is used to ensure that the correct version is sent to the client regardless of caching,
+ *                               and so should be included if a version number is available and makes sense for the script.
+ * @param bool        $in_footer Optional. Whether to enqueue the script before </head> or before </body>.
+ *                               Default 'false'. Accepts 'false' or 'true'.
+ *
+ * @return mixed The script filename if one is located. False if not.
+ */
+function bbp_enqueue_script( $handle = '', $file = '', $deps = array(), $ver = false, $in_footer = false ) {
+
+	// Attempt to locate an enqueueable
+	$located = bbp_locate_enqueueable( $file );
+
+	// Enqueue if located
+	if ( ! empty( $located ) ) {
+
+		// Make sure there is always a version
+		if ( empty( $ver ) ) {
+			$ver = bbp_get_version();
+		}
+
+		// Make path to file relative to site URL
+		$located = bbp_urlize_enqueueable( $located );
+
+		// Register the style
+		wp_register_script( $handle, $located, $deps, $ver, $in_footer );
+
+		// Enqueue the style
+		wp_enqueue_script( $handle );
+	}
+
+	return $located;
+}
+
+/**
+ * This is really cool. This function registers a new template stack location,
+ * using WordPress's built in filters API.
+ *
+ * This allows for templates to live in places beyond just the parent/child
+ * relationship, to allow for custom template locations. Used in conjunction
+ * with bbp_locate_template(), this allows for easy template overrides.
+ *
+ * @since 2.2.0 bbPress (r4323)
+ *
+ * @param string $location_callback Callback function that returns the
+ * @param int $priority
+ */
+function bbp_register_template_stack( $location_callback = '', $priority = 10 ) {
+
+	// Bail if no location, or function/method is not callable
+	if ( empty( $location_callback ) || ! is_callable( $location_callback ) ) {
+		return false;
+	}
+
+	// Add location callback to template stack
+	return add_filter( 'bbp_template_stack', $location_callback, (int) $priority );
+}
+
+/**
+ * Deregisters a previously registered template stack location.
+ *
+ * @since 2.3.0 bbPress (r4652)
+ *
+ * @param string $location_callback Callback function that returns the
+ * @param int $priority
+ * @return bool Whether stack was removed
+ */
+function bbp_deregister_template_stack( $location_callback = '', $priority = 10 ) {
+
+	// Bail if no location, or function/method is not callable
+	if ( empty( $location_callback ) || ! is_callable( $location_callback ) ) {
+		return false;
+	}
+
+	// Remove location callback to template stack
+	return remove_filter( 'bbp_template_stack', $location_callback, (int) $priority );
+}
+
+/**
+ * Call the functions added to the 'bbp_template_stack' filter hook, and return
+ * an array of the template locations.
+ *
+ * @since 2.2.0 bbPress (r4323)
+ * @since 2.6.0 bbPress (r5944) Added support for `WP_Hook`
+ *
+ * @global array $wp_filter Stores all of the filters
+ * @global array $merged_filters Merges the filter hooks using this function.
+ * @global array $wp_current_filter stores the list of current filters with the current one last
+ *
+ * @return array The filtered value after all hooked functions are applied to it.
+ */
+function bbp_get_template_stack() {
+	global $wp_filter, $merged_filters, $wp_current_filter;
+
+	// Setup some default variables
+	$tag  = 'bbp_template_stack';
+	$args = $stack = array();
+
+	// Add 'bbp_template_stack' to the current filter array
+	$wp_current_filter[] = $tag;
+
+	// Bail if no stack setup
+	if ( empty( $wp_filter[ $tag ] ) ) {
+		return array();
+	}
+
+	// Check if WP_Hook class exists, see #WP17817
+	if ( class_exists( 'WP_Hook' ) ) {
+		$filter = $wp_filter[ $tag ]->callbacks;
+	} else {
+		$filter = &$wp_filter[ $tag ];
+
+		// Sort
+		if ( ! isset( $merged_filters[ $tag ] ) ) {
+			ksort( $filter );
+			$merged_filters[ $tag ] = true;
+		}
+	}
+
+	// Ensure we're always at the beginning of the filter array
+	reset( $filter );
+
+	// Loop through 'bbp_template_stack' filters, and call callback functions
+	do {
+		foreach ( (array) current( $filter ) as $the_ ) {
+			if ( ! is_null( $the_['function'] ) ) {
+				$args[1] = $stack;
+				$stack[] = call_user_func_array( $the_['function'], array_slice( $args, 1, (int) $the_['accepted_args'] ) );
+			}
+		}
+	} while ( next( $filter ) !== false );
+
+	// Remove 'bbp_template_stack' from the current filter array
+	array_pop( $wp_current_filter );
+
+	// Remove empties and duplicates
+	$stack = array_unique( array_filter( $stack ) );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_template_stack', $stack ) ;
+}
+
+/**
+ * Get a template part in an output buffer, and return it
+ *
+ * @since 2.4.0 bbPress (r5043)
+ *
+ * @param string $slug
+ * @param string $name
+ * @return string
+ */
+function bbp_buffer_template_part( $slug, $name = null, $echo = true ) {
+	ob_start();
+
+	bbp_get_template_part( $slug, $name );
+
+	// Get the output buffer contents
+	$output = ob_get_clean();
+
+	// Echo or return the output buffer contents
+	if ( true === $echo ) {
+		echo $output;
+	} else {
+		return $output;
+	}
+}
+
+/**
+ * Retrieve path to a template
+ *
+ * Used to quickly retrieve the path of a template without including the file
+ * extension. It will also check the parent theme and theme-compat theme with
+ * the use of {@link bbp_locate_template()}. Allows for more generic template
+ * locations without the use of the other get_*_template() functions.
+ *
+ * @since 2.1.0 bbPress (r3629)
+ *
+ * @param string $type Filename without extension.
+ * @param array $templates An optional list of template candidates
+ * @return string Full path to file.
+ */
+function bbp_get_query_template( $type, $templates = array() ) {
+	$type = preg_replace( '|[^a-z0-9-]+|', '', $type );
+
+	// Fallback template
+	if ( empty( $templates ) ) {
+		$templates = array( "{$type}.php" );
+	}
+
+	// Filter possible templates
+	$templates = apply_filters( "bbp_get_{$type}_template", $templates );
+
+	// Stash the possible templates for this query, for later use
+	bbp_set_theme_compat_templates( $templates );
+
+	// Try to locate a template in the stack
+	$template = bbp_locate_template( $templates );
+
+	// Stash the located template for this query, for later use
+	bbp_set_theme_compat_template( $template );
+
+	// Filter & return
+	return apply_filters( "bbp_{$type}_template", $template, $templates );
+}
+
+/**
+ * Get the possible subdirectories to check for templates in
+ *
+ * @since 2.1.0 bbPress (r3738)
+ *
+ * @param array $templates Templates we are looking for
+ * @return array Possible subdirectories to look in
+ */
+function bbp_get_template_locations( $templates = array() ) {
+	$locations = array(
+		'bbpress',
+		'forums',
+		''
+	);
+
+	// Filter & return
+	return apply_filters( 'bbp_get_template_locations', $locations, $templates );
+}
+
+/**
+ * Add template locations to template files being searched for
+ *
+ * @since 2.1.0 bbPress (r3738)
+ *
+ * @param array $stacks
+ * @return array()
+ */
+function bbp_add_template_stack_locations( $stacks = array() ) {
+	$retval = array();
+
+	// Get alternate locations
+	$locations = bbp_get_template_locations();
+
+	// Loop through locations and stacks and combine
+	foreach ( (array) $stacks as $stack ) {
+		foreach ( (array) $locations as $custom_location ) {
+			$retval[] = untrailingslashit( trailingslashit( $stack ) . $custom_location );
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_add_template_stack_locations', array_unique( $retval ), $stacks );
+}
+
+/**
+ * Add checks for bbPress conditions to parse_query action
+ *
+ * If it's a user page, WP_Query::bbp_is_single_user is set to true.
+ *
+ * If it's a user edit page, WP_Query::bbp_is_single_user_edit is set to true
+ * and the the 'wp-admin/includes/user.php' file is included.
+ *
+ * In addition, on user/user edit pages, WP_Query::home is set to false & query
+ * vars 'bbp_user_id' with the displayed user id is added.
+ *
+ * In 2.6.0, the 'author_name' variable is no longer set when viewing a single
+ * user, because of is_author() weirdness. If this removal causes problems, it
+ * may come back in a future release.
+ *
+ * If it's a forum edit, WP_Query::bbp_is_forum_edit is set to true
+ * If it's a topic edit, WP_Query::bbp_is_topic_edit is set to true
+ * If it's a reply edit, WP_Query::bbp_is_reply_edit is set to true.
+ *
+ * If it's a view page, WP_Query::bbp_is_view is set to true
+ * If it's a search page, WP_Query::bbp_is_search is set to true
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param WP_Query $posts_query
+ */
+function bbp_parse_query( $posts_query ) {
+
+	// Bail if $posts_query is not the main loop
+	if ( ! $posts_query->is_main_query() ) {
+		return;
+	}
+
+	// Bail if filters are suppressed on this query
+	if ( true === $posts_query->get( 'suppress_filters' ) ) {
+		return;
+	}
+
+	// Bail if in admin
+	if ( is_admin() ) {
+		return;
+	}
+
+	// Get query variables (default to null if not set)
+	$bbp_view  = $posts_query->get( bbp_get_view_rewrite_id(),   null );
+	$bbp_user  = $posts_query->get( bbp_get_user_rewrite_id(),   null );
+	$is_edit   = $posts_query->get( bbp_get_edit_rewrite_id(),   null );
+	$is_search = $posts_query->get( bbp_get_search_rewrite_id(), null );
+
+	// It is a user page - We'll also check if it is user edit
+	if ( ! is_null( $bbp_user ) ) {
+
+		/** Find User *********************************************************/
+
+		// Setup the default user variable
+		$the_user = false;
+
+		// If using pretty permalinks, always use slug
+		if ( get_option( 'permalink_structure' ) ) {
+			$the_user = get_user_by( 'slug', $bbp_user );
+
+		// If not using pretty permalinks, always use numeric ID
+		} elseif ( is_numeric( $bbp_user ) ) {
+			$the_user = get_user_by( 'id', $bbp_user );
+		}
+
+		// 404 and bail if user does not have a profile
+		if ( empty( $the_user->ID ) || ! bbp_user_has_profile( $the_user->ID ) ) {
+			$posts_query->bbp_is_404 = true;
+			return;
+		}
+
+		/** User Exists *******************************************************/
+
+		$is_favs        = $posts_query->get( bbp_get_user_favorites_rewrite_id()     );
+		$is_subs        = $posts_query->get( bbp_get_user_subscriptions_rewrite_id() );
+		$is_topics      = $posts_query->get( bbp_get_user_topics_rewrite_id()        );
+		$is_replies     = $posts_query->get( bbp_get_user_replies_rewrite_id()       );
+		$is_engagements = $posts_query->get( bbp_get_user_engagements_rewrite_id()   );
+
+		// View or edit?
+		if ( ! is_null( $is_edit ) ) {
+
+			// We are editing a profile
+			$posts_query->bbp_is_single_user_edit = true;
+
+			// Load the core WordPress contact methods
+			if ( ! function_exists( '_wp_get_user_contactmethods' ) ) {
+				require_once ABSPATH . 'wp-includes/registration.php';
+			}
+
+			// Load the edit_user functions
+			if ( ! function_exists( 'edit_user' ) ) {
+				require_once ABSPATH . 'wp-admin/includes/user.php';
+			}
+
+			// Load the grant/revoke super admin functions
+			if ( is_multisite() && ! function_exists( 'revoke_super_admin' ) ) {
+				require_once ABSPATH . 'wp-admin/includes/ms.php';
+			}
+
+			// Editing a user
+			$posts_query->bbp_is_edit = true;
+
+		// User favorites
+		} elseif ( ! empty( $is_favs ) ) {
+			$posts_query->bbp_is_single_user_favs = true;
+
+		// User subscriptions
+		} elseif ( ! empty( $is_subs ) ) {
+			$posts_query->bbp_is_single_user_subs = true;
+
+		// User topics
+		} elseif ( ! empty( $is_topics ) ) {
+			$posts_query->bbp_is_single_user_topics = true;
+
+		// User topics
+		} elseif ( ! empty( $is_replies ) ) {
+			$posts_query->bbp_is_single_user_replies = true;
+
+		// User engagements
+		} elseif ( ! empty( $is_engagements ) ) {
+			$posts_query->bbp_is_single_user_engagements = true;
+
+		// User profile
+		} else {
+			$posts_query->bbp_is_single_user_profile = true;
+		}
+
+		// Make sure 404 is not set
+		$posts_query->is_404  = false;
+
+		// Correct is_home variable
+		$posts_query->is_home = false;
+
+		// Looking at a single user
+		$posts_query->bbp_is_single_user = true;
+
+		// User found so don't 404 yet
+		$posts_query->bbp_is_404 = false;
+
+		// User is looking at their own profile
+		if ( bbp_get_current_user_id() === $the_user->ID ) {
+			$posts_query->bbp_is_single_user_home = true;
+		}
+
+		// Set bbp_user_id for future reference
+		$posts_query->set( 'bbp_user_id', $the_user->ID );
+
+		// Set the displayed user global to this user
+		bbpress()->displayed_user = $the_user;
+
+	// View Page
+	} elseif ( ! is_null( $bbp_view ) ) {
+
+		// Check if the view exists by checking if there are query args are set
+		$view_args = bbp_get_view_query_args( $bbp_view );
+
+		// Bail if view args are empty
+		if ( empty( $view_args ) ) {
+			$posts_query->bbp_is_404 = true;
+			return;
+		}
+
+		// Correct is_home variable
+		$posts_query->is_home     = false;
+
+		// We are in a custom topic view
+		$posts_query->bbp_is_view = true;
+
+		// No 404 because views are all (currently) public
+		$posts_query->bbp_is_404 = false;
+
+	// Search Page
+	} elseif ( ! is_null( $is_search ) ) {
+
+		// Check if there are search query args set
+		$search_terms = bbp_get_search_terms();
+		if ( ! empty( $search_terms ) ) {
+			$posts_query->bbp_search_terms = $search_terms;
+		}
+
+		// Correct is_home variable
+		$posts_query->is_home = false;
+
+		// We are in a search query
+		$posts_query->bbp_is_search = true;
+
+		// No 404 because search is always public
+		$posts_query->bbp_is_404 = false;
+
+	// Forum/Topic/Reply Edit Page
+	} elseif ( ! is_null( $is_edit ) ) {
+
+		// Get the post type from the main query loop
+		$post_type = $posts_query->get( 'post_type' );
+
+		// Check which post_type we are editing, if any
+		if ( ! empty( $post_type ) ) {
+			switch ( $post_type ) {
+
+				// We are editing a forum
+				case bbp_get_forum_post_type() :
+					$posts_query->bbp_is_forum_edit  = true;
+					$posts_query->bbp_is_edit        = true;
+					$posts_query->bbp_is_404         = false;
+					break;
+
+				// We are editing a topic
+				case bbp_get_topic_post_type() :
+					$posts_query->bbp_is_topic_edit  = true;
+					$posts_query->bbp_is_edit        = true;
+					$posts_query->bbp_is_404         = false;
+					break;
+
+				// We are editing a reply
+				case bbp_get_reply_post_type() :
+					$posts_query->bbp_is_reply_edit  = true;
+					$posts_query->bbp_is_edit        = true;
+					$posts_query->bbp_is_404         = false;
+					break;
+			}
+
+		// We are editing a topic tag
+		} elseif ( bbp_is_topic_tag() ) {
+			$posts_query->bbp_is_topic_tag_edit = true;
+			$posts_query->bbp_is_edit           = true;
+			$posts_query->bbp_is_404            = false;
+		}
+
+		// We save post revisions on our own
+		remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Topic tag page
+	} elseif ( bbp_is_topic_tag() ) {
+		$posts_query->set( 'bbp_topic_tag',  get_query_var( 'term' )   );
+		$posts_query->set( 'post_type',      bbp_get_topic_post_type() );
+		$posts_query->set( 'posts_per_page', bbp_get_topics_per_page() );
+
+	// Do topics on forums root
+	} elseif ( is_post_type_archive( bbp_get_post_types( array( 'has_archive' => true ) ) ) && ( 'topics' === bbp_show_on_root() ) ) {
+		$posts_query->bbp_show_topics_on_root = true;
+	}
+}
Index: /tags/2.6.6/src/includes/core/template-loader.php
===================================================================
--- /tags/2.6.6/src/includes/core/template-loader.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/template-loader.php	(revision 7162)
@@ -0,0 +1,472 @@
+<?php
+
+/**
+ * bbPress Template Loader
+ *
+ * @package bbPress
+ * @subpackage TemplateLoader
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Possibly intercept the template being loaded
+ *
+ * Listens to the 'template_include' filter and waits for any bbPress specific
+ * template condition to be met. If one is met and the template file exists,
+ * it will be used; otherwise
+ *
+ * Note that the _edit() checks are ahead of their counterparts, to prevent them
+ * from being stomped on accident.
+ *
+ * @since 2.0.0 bbPress (r3032)
+ *
+ * @param string $template
+ *
+ * @return string The path to the template file that is being used
+ */
+function bbp_template_include_theme_supports( $template = '' ) {
+
+	// Editing a user
+	if     ( bbp_is_single_user_edit() && ( $new_template = bbp_get_single_user_edit_template() ) ) :
+
+	// User favorites
+	elseif ( bbp_is_favorites()        && ( $new_template = bbp_get_favorites_template()        ) ) :
+
+	// User favorites
+	elseif ( bbp_is_subscriptions()    && ( $new_template = bbp_get_subscriptions_template()    ) ) :
+
+	// Viewing a user
+	elseif ( bbp_is_single_user()      && ( $new_template = bbp_get_single_user_template()      ) ) :
+
+	// Single View
+	elseif ( bbp_is_single_view()      && ( $new_template = bbp_get_single_view_template()      ) ) :
+
+	// Search
+	elseif ( bbp_is_search()           && ( $new_template = bbp_get_search_template()           ) ) :
+
+	// Forum edit
+	elseif ( bbp_is_forum_edit()       && ( $new_template = bbp_get_forum_edit_template()       ) ) :
+
+	// Single Forum
+	elseif ( bbp_is_single_forum()     && ( $new_template = bbp_get_single_forum_template()     ) ) :
+
+	// Forum Archive
+	elseif ( bbp_is_forum_archive()    && ( $new_template = bbp_get_forum_archive_template()    ) ) :
+
+	// Topic merge
+	elseif ( bbp_is_topic_merge()      && ( $new_template = bbp_get_topic_merge_template()      ) ) :
+
+	// Topic split
+	elseif ( bbp_is_topic_split()      && ( $new_template = bbp_get_topic_split_template()      ) ) :
+
+	// Topic edit
+	elseif ( bbp_is_topic_edit()       && ( $new_template = bbp_get_topic_edit_template()       ) ) :
+
+	// Single Topic
+	elseif ( bbp_is_single_topic()     && ( $new_template = bbp_get_single_topic_template()     ) ) :
+
+	// Topic Archive
+	elseif ( bbp_is_topic_archive()    && ( $new_template = bbp_get_topic_archive_template()    ) ) :
+
+	// Reply move
+	elseif ( bbp_is_reply_move()       && ( $new_template = bbp_get_reply_move_template()       ) ) :
+
+	// Editing a reply
+	elseif ( bbp_is_reply_edit()       && ( $new_template = bbp_get_reply_edit_template()       ) ) :
+
+	// Single Reply
+	elseif ( bbp_is_single_reply()     && ( $new_template = bbp_get_single_reply_template()     ) ) :
+
+	// Editing a topic tag
+	elseif ( bbp_is_topic_tag_edit()   && ( $new_template = bbp_get_topic_tag_edit_template()   ) ) :
+
+	// Viewing a topic tag
+	elseif ( bbp_is_topic_tag()        && ( $new_template = bbp_get_topic_tag_template()        ) ) :
+	endif;
+
+	// A bbPress template file was located, so override the WordPress template
+	// and use it to switch off theme compatibility.
+	if ( ! empty( $new_template ) ) {
+		$template = bbp_set_template_included( $new_template );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_template_include_theme_supports', $template );
+}
+
+/**
+ * Set the included template
+ *
+ * @since 2.4.0 bbPress (r4975)
+ *
+ * @param mixed $template Default false
+ * @return mixed False if empty. Template name if template included
+ */
+function bbp_set_template_included( $template = false ) {
+	bbpress()->theme_compat->bbpress_template = $template;
+
+	return bbpress()->theme_compat->bbpress_template;
+}
+
+/**
+ * Is a bbPress template being included?
+ *
+ * @since 2.4.0 bbPress (r4975)
+ *
+ * @return bool True if yes, false if no
+ */
+function bbp_is_template_included() {
+	return ! empty( bbpress()->theme_compat->bbpress_template );
+}
+
+/** Custom Functions **********************************************************/
+
+/**
+ * Attempt to load a custom bbPress functions file, similar to each themes
+ * functions.php file.
+ *
+ * @since 2.1.0 bbPress (r3732)
+ *
+ * @global string $pagenow
+ */
+function bbp_load_theme_functions() {
+	global $pagenow;
+
+	// If bbPress is being deactivated, do not load any more files
+	if ( bbp_is_deactivation() ) {
+		return;
+	}
+
+	if ( ! defined( 'WP_INSTALLING' ) || ( ! empty( $pagenow ) && ( 'wp-activate.php' !== $pagenow ) ) ) {
+		bbp_locate_template( 'bbpress-functions.php', true );
+	}
+}
+
+/** Individual Templates ******************************************************/
+
+/**
+ * Get the user profile template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_user_template() {
+	$nicename  = bbp_get_displayed_user_field( 'user_nicename' );
+	$user_id   = bbp_get_displayed_user_id();
+	$templates = array(
+		'single-user-' . $nicename . '.php', // Single User nicename
+		'single-user-' . $user_id  . '.php', // Single User ID
+		'single-user.php',                   // Single User
+		'user.php',                          // User
+	);
+	return bbp_get_query_template( 'profile', $templates );
+}
+
+/**
+ * Get the user profile edit template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_user_edit_template() {
+	$nicename  = bbp_get_displayed_user_field( 'user_nicename' );
+	$user_id   = bbp_get_displayed_user_id();
+	$templates = array(
+		'single-user-edit-' . $nicename . '.php', // Single User Edit nicename
+		'single-user-edit-' . $user_id  . '.php', // Single User Edit ID
+		'single-user-edit.php',                   // Single User Edit
+		'user-edit.php',                          // User Edit
+		'user.php',                               // User
+	);
+	return bbp_get_query_template( 'profile_edit', $templates );
+}
+
+/**
+ * Get the user favorites template
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_favorites_template() {
+	$nicename  = bbp_get_displayed_user_field( 'user_nicename' );
+	$user_id   = bbp_get_displayed_user_id();
+	$templates = array(
+		'single-user-favorites-' . $nicename . '.php', // Single User Favs nicename
+		'single-user-favorites-' . $user_id  . '.php', // Single User Favs ID
+		'favorites-' . $nicename  . '.php',            // Favorites nicename
+		'favorites-' . $user_id   . '.php',            // Favorites ID
+		'favorites.php',                               // Favorites
+		'user.php',                                    // User
+	);
+	return bbp_get_query_template( 'favorites', $templates );
+}
+
+/**
+ * Get the user subscriptions template
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_subscriptions_template() {
+	$nicename  = bbp_get_displayed_user_field( 'user_nicename' );
+	$user_id   = bbp_get_displayed_user_id();
+	$templates = array(
+		'single-user-subscriptions-' . $nicename . '.php', // Single User Subs nicename
+		'single-user-subscriptions-' . $user_id  . '.php', // Single User Subs ID
+		'subscriptions-' . $nicename  . '.php',            // Subscriptions nicename
+		'subscriptions-' . $user_id   . '.php',            // Subscriptions ID
+		'subscriptions.php',                               // Subscriptions
+		'user.php',                                        // User
+	);
+	return bbp_get_query_template( 'subscriptions', $templates );
+}
+
+/**
+ * Get the view template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_view_template() {
+	$view_id   = bbp_get_view_id();
+	$templates = array(
+		'single-view-' . $view_id . '.php', // Single View ID
+		'view-'        . $view_id . '.php', // View ID
+		'single-view.php',                  // Single View
+		'view.php',                         // View
+	);
+	return bbp_get_query_template( 'single_view', $templates );
+}
+
+/**
+ * Get the search template
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_search_template() {
+	$templates = array(
+		'page-forum-search.php', // Single Search
+		'forum-search.php',      // Search
+	);
+	return bbp_get_query_template( 'single_search', $templates );
+}
+
+/**
+ * Get the single forum template
+ *
+ * @since 2.1.0 bbPress (r3922)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_forum_template() {
+	$templates = array(
+		'single-' . bbp_get_forum_post_type() . '.php' // Single Forum
+	);
+	return bbp_get_query_template( 'single_forum', $templates );
+}
+
+/**
+ * Get the forum archive template
+ *
+ * @since 2.1.0 bbPress (r3922)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_forum_archive_template() {
+	$templates = array(
+		'archive-' . bbp_get_forum_post_type() . '.php' // Forum Archive
+	);
+	return bbp_get_query_template( 'forum_archive', $templates );
+}
+
+/**
+ * Get the forum edit template
+ *
+ * @since 2.1.0 bbPress (r3566)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_forum_edit_template() {
+	$templates = array(
+		'single-' . bbp_get_forum_post_type() . '-edit.php' // Single Forum Edit
+	);
+	return bbp_get_query_template( 'forum_edit', $templates );
+}
+
+/**
+ * Get the single topic template
+ *
+ * @since 2.1.0 bbPress (r3922)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_topic_template() {
+	$templates = array(
+		'single-' . bbp_get_topic_post_type() . '.php'
+	);
+	return bbp_get_query_template( 'single_topic', $templates );
+}
+
+/**
+ * Get the topic archive template
+ *
+ * @since 2.1.0 bbPress (r3922)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_archive_template() {
+	$templates = array(
+		'archive-' . bbp_get_topic_post_type() . '.php' // Topic Archive
+	);
+	return bbp_get_query_template( 'topic_archive', $templates );
+}
+
+/**
+ * Get the topic edit template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_edit_template() {
+	$templates = array(
+		'single-' . bbp_get_topic_post_type() . '-edit.php' // Single Topic Edit
+	);
+	return bbp_get_query_template( 'topic_edit', $templates );
+}
+
+/**
+ * Get the topic split template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_split_template() {
+	$templates = array(
+		'single-' . bbp_get_topic_post_type() . '-split.php', // Topic Split
+	);
+	return bbp_get_query_template( 'topic_split', $templates );
+}
+
+/**
+ * Get the topic merge template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_merge_template() {
+	$templates = array(
+		'single-' . bbp_get_topic_post_type() . '-merge.php', // Topic Merge
+	);
+	return bbp_get_query_template( 'topic_merge', $templates );
+}
+
+/**
+ * Get the single reply template
+ *
+ * @since 2.1.0 bbPress (r3922)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_single_reply_template() {
+	$templates = array(
+		'single-' . bbp_get_reply_post_type() . '.php'
+	);
+	return bbp_get_query_template( 'single_reply', $templates );
+}
+
+/**
+ * Get the reply edit template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+* @return string Path to template file
+ */
+function bbp_get_reply_edit_template() {
+	$templates = array(
+		'single-' . bbp_get_reply_post_type() . '-edit.php' // Single Reply Edit
+	);
+	return bbp_get_query_template( 'reply_edit', $templates );
+}
+
+/**
+ * Get the reply move template
+ *
+ * @since 2.3.0 bbPress (r4521)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_reply_move_template() {
+	$templates = array(
+		'single-' . bbp_get_reply_post_type() . '-move.php', // Reply move
+	);
+	return bbp_get_query_template( 'reply_move', $templates );
+}
+
+/**
+ * Get the topic template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_tag_template() {
+	$tt_slug   = bbp_get_topic_tag_slug();
+	$tt_id     = bbp_get_topic_tag_tax_id();
+	$templates = array(
+		'taxonomy-' . $tt_slug . '.php', // Single Topic Tag slug
+		'taxonomy-' . $tt_id   . '.php', // Single Topic Tag ID
+	);
+	return bbp_get_query_template( 'topic_tag', $templates );
+}
+
+/**
+ * Get the topic edit template
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @return string Path to template file
+ */
+function bbp_get_topic_tag_edit_template() {
+	$tt_slug   = bbp_get_topic_tag_slug();
+	$tt_id     = bbp_get_topic_tag_tax_id();
+	$templates = array(
+		'taxonomy-' . $tt_slug . '-edit.php', // Single Topic Tag Edit slug
+		'taxonomy-' . $tt_id   . '-edit.php'  // Single Topic Tag Edit ID
+	);
+	return bbp_get_query_template( 'topic_tag_edit', $templates );
+}
+
+/**
+ * Get the templates to use as the endpoint for bbPress template parts
+ *
+ * @since 2.0.0 bbPress (r3311)
+ * @since 2.6.0 bbPress (r5950) Added `singular.php` to template stack
+ *
+ * @return string Path to template file
+ */
+function bbp_get_theme_compat_templates() {
+	$templates = array(
+		'plugin-bbpress.php',
+		'bbpress.php',
+		'forums.php',
+		'forum.php',
+		'generic.php',
+		'page.php',
+		'single.php',
+		'singular.php',
+		'index.php'
+	);
+	return bbp_get_query_template( 'bbpress', $templates );
+}
Index: /tags/2.6.6/src/includes/core/theme-compat.php
===================================================================
--- /tags/2.6.6/src/includes/core/theme-compat.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/theme-compat.php	(revision 7162)
@@ -0,0 +1,1060 @@
+<?php
+
+/**
+ * bbPress Theme Compatibility
+ *
+ * @package bbPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Theme Compat **************************************************************/
+
+/**
+ * What follows is an attempt at intercepting the natural page load process
+ * to replace the_content() with the appropriate bbPress content.
+ *
+ * To do this, bbPress does several direct manipulations of global variables
+ * and forces them to do what they are not supposed to be doing.
+ *
+ * Don't try anything you're about to witness here, at home. Ever.
+ */
+
+/** Base Class ****************************************************************/
+
+/**
+ * Theme Compatibility base class
+ *
+ * This is only intended to be extended, and is included here as a basic guide
+ * for future Template Packs to use. @link bbp_setup_theme_compat()
+ *
+ * @since 2.0.0 bbPress (r3506)
+ */
+class BBP_Theme_Compat {
+
+	/**
+	 * Should be like:
+	 *
+	 * array(
+	 *     'id'      => ID of the theme (should be unique)
+	 *     'name'    => Name of the theme (should match style.css)
+	 *     'version' => Theme version for cache busting scripts and styling
+	 *     'dir'     => Path to theme
+	 *     'url'     => URL to theme
+	 * );
+	 * @var array
+	 */
+	private $_data = array();
+
+	/**
+	 * Pass the $properties to the object on creation.
+	 *
+	 * @since 2.1.0 bbPress (r3926)
+	 *
+	 * @param array $properties
+	 */
+	public function __construct( Array $properties = array() ) {
+		$this->_data = $properties;
+	}
+
+	/**
+	 * Set a theme's property.
+	 *
+	 * @since 2.1.0 bbPress (r3926)
+	 *
+	 * @param string $property
+	 * @param mixed $value
+	 * @return mixed
+	 */
+	public function __set( $property, $value ) {
+		return $this->_data[ $property ] = $value;
+	}
+
+	/**
+	 * Get a theme's property.
+	 *
+	 * @since 2.1.0 bbPress (r3926)
+	 *
+	 * @param string $property
+	 * @param mixed $value
+	 * @return mixed
+	 */
+	public function __get( $property ) {
+		return array_key_exists( $property, $this->_data )
+			? $this->_data[ $property ]
+			: '';
+	}
+
+	/**
+	 * Return the template directory.
+	 *
+	 * @since 2.6.0 bbPress (r6548)
+	 *
+	 * @return string
+	 */
+	public function get_dir() {
+		return $this->dir;
+	}
+}
+
+/** Functions *****************************************************************/
+
+/**
+ * Setup the active template pack and register it's directory in the stack.
+ *
+ * @since 2.0.0 bbPress (r3311)
+ *
+ * @param BBP_Theme_Compat $theme
+ */
+function bbp_setup_theme_compat( $theme = 'default' ) {
+	$bbp = bbpress();
+
+	// Bail if something already has this under control
+	if ( ! empty( $bbp->theme_compat->theme ) ) {
+		return;
+	}
+
+	// Fallback for empty theme
+	if ( empty( $theme ) ) {
+		$theme = 'default';
+	}
+
+	// If the theme is registered, use it and add it to the stack
+	if ( isset( $bbp->theme_compat->packages[ $theme ] ) ) {
+		$bbp->theme_compat->theme = $bbp->theme_compat->packages[ $theme ];
+
+		// Setup the template stack for the active template pack
+		bbp_register_template_stack( array( $bbp->theme_compat->theme, 'get_dir' ) );
+	}
+}
+
+/**
+ * Get the current template pack package.
+ *
+ * @since 2.6.0 bbPress (r6548)
+ *
+ * @return BBP_Theme_Compat
+ */
+function bbp_get_current_template_pack() {
+	$bbp = bbpress();
+
+	// Theme was not setup, so fallback to an empty object
+	if ( empty( $bbp->theme_compat->theme ) ) {
+		$bbp->theme_compat->theme = new BBP_Theme_Compat();
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_current_template_pack', $bbp->theme_compat->theme );
+}
+
+/**
+ * Gets the id of the bbPress compatible theme used, in the event the
+ * currently active WordPress theme does not explicitly support bbPress.
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own bbPress compatibility layers for their themes.
+ *
+ * @since 2.0.0 bbPress (r3506)
+ *
+ * @return string
+ */
+function bbp_get_theme_compat_id() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_compat_id', bbp_get_current_template_pack()->id );
+}
+
+/**
+ * Gets the name of the bbPress compatible theme used, in the event the
+ * currently active WordPress theme does not explicitly support bbPress.
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own bbPress compatibility layers for their themes.
+ *
+ * @since 2.0.0 bbPress (r3506)
+ *
+ * @return string
+ */
+function bbp_get_theme_compat_name() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_compat_name', bbp_get_current_template_pack()->name );
+}
+
+/**
+ * Gets the version of the bbPress compatible theme used, in the event the
+ * currently active WordPress theme does not explicitly support bbPress.
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own bbPress compatibility layers for their themes.
+ *
+ * @since 2.0.0 bbPress (r3506)
+ *
+ * @return string
+ */
+function bbp_get_theme_compat_version() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_compat_version', bbp_get_current_template_pack()->version );
+}
+
+/**
+ * Gets the bbPress compatible theme used in the event the currently active
+ * WordPress theme does not explicitly support bbPress. This can be filtered,
+ * or set manually. Tricky theme authors can override the default and include
+ * their own bbPress compatibility layers for their themes.
+ *
+ * @since 2.0.0 bbPress (r3032)
+ *
+ * @return string
+ */
+function bbp_get_theme_compat_dir() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_compat_dir', bbp_get_current_template_pack()->dir );
+}
+
+/**
+ * Gets the bbPress compatible theme used in the event the currently active
+ * WordPress theme does not explicitly support bbPress. This can be filtered,
+ * or set manually. Tricky theme authors can override the default and include
+ * their own bbPress compatibility layers for their themes.
+ *
+ * @since 2.0.0 bbPress (r3032)
+ *
+ * @return string
+ */
+function bbp_get_theme_compat_url() {
+
+	// Filter & return
+	return apply_filters( 'bbp_get_theme_compat_url', bbp_get_current_template_pack()->url );
+}
+
+/**
+ * Gets true/false if page is currently inside theme compatibility
+ *
+ * @since 2.0.0 bbPress (r3265)
+ *
+ * @return bool
+ */
+function bbp_is_theme_compat_active() {
+	$bbp = bbpress();
+
+	if ( empty( $bbp->theme_compat->active ) ) {
+		return false;
+	}
+
+	return $bbp->theme_compat->active;
+}
+
+/**
+ * Sets true/false if page is currently inside theme compatibility
+ *
+ * @since 2.0.0 bbPress (r3265)
+ *
+ * @param bool $set
+ * @return bool
+ */
+function bbp_set_theme_compat_active( $set = true ) {
+	bbpress()->theme_compat->active = $set;
+
+	return (bool) bbpress()->theme_compat->active;
+}
+
+/**
+ * Set the theme compat templates global
+ *
+ * Stash possible template files for the current query. Useful if plugins want
+ * to override them, or see what files are being scanned for inclusion.
+ *
+ * @since 2.0.0 bbPress (r3311)
+ */
+function bbp_set_theme_compat_templates( $templates = array() ) {
+	bbpress()->theme_compat->templates = $templates;
+
+	return bbpress()->theme_compat->templates;
+}
+
+/**
+ * Set the theme compat template global
+ *
+ * Stash the template file for the current query. Useful if plugins want
+ * to override it, or see what file is being included.
+ *
+ * @since 2.0.0 bbPress (r3311)
+ */
+function bbp_set_theme_compat_template( $template = '' ) {
+	bbpress()->theme_compat->template = $template;
+
+	return bbpress()->theme_compat->template;
+}
+
+/**
+ * Set the theme compat original_template global
+ *
+ * Stash the original template file for the current query. Useful for checking
+ * if bbPress was able to find a more appropriate template.
+ *
+ * @since 2.1.0 bbPress (r3926)
+ */
+function bbp_set_theme_compat_original_template( $template = '' ) {
+	bbpress()->theme_compat->original_template = $template;
+
+	return bbpress()->theme_compat->original_template;
+}
+
+/**
+ * Is a template the original_template global
+ *
+ * Stash the original template file for the current query. Useful for checking
+ * if bbPress was able to find a more appropriate template.
+ *
+ * @since 2.1.0 bbPress (r3926)
+ */
+function bbp_is_theme_compat_original_template( $template = '' ) {
+	$bbp = bbpress();
+
+	// Bail if no original template
+	if ( empty( $bbp->theme_compat->original_template ) ) {
+		return false;
+	}
+
+	return (bool) ( $bbp->theme_compat->original_template === $template );
+}
+
+/**
+ * Register a new bbPress theme package to the active theme packages array
+ *
+ * @since 2.1.0 bbPress (r3829)
+ *
+ * @param array $theme
+ */
+function bbp_register_theme_package( $theme = array(), $override = true ) {
+
+	// Create new BBP_Theme_Compat object from the $theme array
+	if ( is_array( $theme ) ) {
+		$theme = new BBP_Theme_Compat( $theme );
+	}
+
+	// Bail if $theme isn't a proper object
+	if ( ! is_a( $theme, 'BBP_Theme_Compat' ) ) {
+		return;
+	}
+
+	// Load up bbPress
+	$bbp = bbpress();
+
+	// Only override if the flag is set and not previously registered
+	if ( empty( $bbp->theme_compat->packages[ $theme->id ] ) || ( true === $override ) ) {
+		$bbp->theme_compat->packages[ $theme->id ] = $theme;
+	}
+}
+
+/**
+ * This fun little function fills up some WordPress globals with dummy data to
+ * stop your average page template from complaining about it missing.
+ *
+ * @since 2.0.0 bbPress (r3108)
+ *
+ * @global WP_Query $wp_query
+ * @global object $post
+ * @param array $args
+ */
+function bbp_theme_compat_reset_post( $args = array() ) {
+	global $wp_query, $post;
+
+	// Switch defaults if post is set
+	if ( isset( $wp_query->post ) ) {
+
+		// Use primarily Post attributes
+		$defaults = array(
+			'ID'                    => $wp_query->post->ID,
+			'post_status'           => $wp_query->post->post_status,
+			'post_author'           => $wp_query->post->post_author,
+			'post_parent'           => $wp_query->post->post_parent,
+			'post_type'             => $wp_query->post->post_type,
+			'post_date'             => $wp_query->post->post_date,
+			'post_date_gmt'         => $wp_query->post->post_date_gmt,
+			'post_modified'         => $wp_query->post->post_modified,
+			'post_modified_gmt'     => $wp_query->post->post_modified_gmt,
+			'post_content'          => $wp_query->post->post_content,
+			'post_title'            => $wp_query->post->post_title,
+			'post_excerpt'          => $wp_query->post->post_excerpt,
+			'post_content_filtered' => $wp_query->post->post_content_filtered,
+			'post_mime_type'        => $wp_query->post->post_mime_type,
+			'post_password'         => $wp_query->post->post_password,
+			'post_name'             => $wp_query->post->post_name,
+			'guid'                  => $wp_query->post->guid,
+			'menu_order'            => $wp_query->post->menu_order,
+			'pinged'                => $wp_query->post->pinged,
+			'to_ping'               => $wp_query->post->to_ping,
+			'ping_status'           => $wp_query->post->ping_status,
+			'comment_status'        => $wp_query->post->comment_status,
+			'comment_count'         => $wp_query->post->comment_count,
+			'filter'                => $wp_query->post->filter,
+
+			'is_404'                => false,
+			'is_page'               => false,
+			'is_single'             => false,
+			'is_archive'            => false,
+			'is_tax'                => false
+		);
+	} else {
+
+		// Get the default zero date value a single time
+		$zero_date = bbp_get_empty_datetime();
+
+		// Use primarily empty attributes
+		$defaults = array(
+			'ID'                    => -9999,
+			'post_status'           => bbp_get_public_status_id(),
+			'post_author'           => 0,
+			'post_parent'           => 0,
+			'post_type'             => 'page',
+			'post_date'             => $zero_date,
+			'post_date_gmt'         => $zero_date,
+			'post_modified'         => $zero_date,
+			'post_modified_gmt'     => $zero_date,
+			'post_content'          => '',
+			'post_title'            => '',
+			'post_excerpt'          => '',
+			'post_content_filtered' => '',
+			'post_mime_type'        => '',
+			'post_password'         => '',
+			'post_name'             => '',
+			'guid'                  => '',
+			'menu_order'            => 0,
+			'pinged'                => '',
+			'to_ping'               => '',
+			'ping_status'           => '',
+			'comment_status'        => 'closed',
+			'comment_count'         => 0,
+			'filter'                => 'raw',
+
+			'is_404'                => false,
+			'is_page'               => false,
+			'is_single'             => false,
+			'is_archive'            => false,
+			'is_tax'                => false
+		);
+	}
+
+	// Parse & filter
+	$dummy = bbp_parse_args( $args, $defaults, 'theme_compat_reset_post' );
+
+	// Bail if dummy post is empty
+	if ( empty( $dummy ) ) {
+		return;
+	}
+
+	// Set the $post global
+	$post = new WP_Post( (object) $dummy );
+
+	// Copy the new post global into the main $wp_query
+	$wp_query->post       = $post;
+	$wp_query->posts      = array( $post );
+
+	// Prevent comments form from appearing
+	$wp_query->post_count = 1;
+	$wp_query->is_404     = $dummy['is_404'];
+	$wp_query->is_page    = $dummy['is_page'];
+	$wp_query->is_single  = $dummy['is_single'];
+	$wp_query->is_archive = $dummy['is_archive'];
+	$wp_query->is_tax     = $dummy['is_tax'];
+
+	// Reset is_singular based on page/single args
+	// https://bbpress.trac.wordpress.org/ticket/2545
+	$wp_query->is_singular = $wp_query->is_single;
+
+	// Clean up the dummy post
+	unset( $dummy );
+
+	// If we are resetting a post, we are in theme compat
+	bbp_set_theme_compat_active( true );
+}
+
+/**
+ * Reset main query vars and filter 'the_content' to output a bbPress
+ * template part as needed.
+ *
+ * @since 2.0.0 bbPress (r3032)
+ *
+ * @param string $template
+ */
+function bbp_template_include_theme_compat( $template = '' ) {
+
+	/**
+	 * Bail if a root template was already found. This prevents unintended
+	 * recursive filtering of 'the_content'.
+	 *
+	 * @link https://bbpress.trac.wordpress.org/ticket/2429
+	 */
+	if ( bbp_is_template_included() ) {
+		return $template;
+	}
+
+	/**
+	 * If BuddyPress is activated at a network level, the action order is
+	 * reversed, which causes the template integration to fail. If we're looking
+	 * at a BuddyPress page here, bail to prevent the extra processing.
+	 *
+	 * This is a bit more brute-force than is probably necessary, but gets the
+	 * job done while we work towards something more elegant.
+	 */
+	if ( function_exists( 'is_buddypress' ) && is_buddypress() ) {
+		return $template;
+	}
+
+	// Define local variable(s)
+	$bbp_shortcodes = bbpress()->shortcodes;
+
+	// Bail if shortcodes are unset somehow
+	if ( ! is_a( $bbp_shortcodes, 'BBP_Shortcodes' ) ) {
+		return $template;
+	}
+
+	/** Users *************************************************************/
+
+	if ( bbp_is_single_user_edit() || bbp_is_single_user() ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => bbp_buffer_template_part( 'content', 'single-user', false ),
+			'post_type'      => '',
+			'post_title'     => bbp_get_displayed_user_field( 'display_name' ),
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => false,
+			'comment_status' => 'closed'
+		) );
+
+	/** Forums ************************************************************/
+
+	// Forum archive
+	} elseif ( bbp_is_forum_archive() ) {
+
+		// Page exists where this archive should be
+		$page = bbp_get_page_by_path( bbp_get_root_slug() );
+
+		// Should we replace the content...
+		if ( empty( $page->post_content ) ) {
+
+			// Use the topics archive
+			if ( 'topics' === bbp_show_on_root() ) {
+				$new_content = $bbp_shortcodes->display_topic_index();
+
+			// No page so show the archive
+			} else {
+				$new_content = $bbp_shortcodes->display_forum_index();
+			}
+
+		// ...or use the existing page content?
+		} else {
+			$new_content = apply_filters( 'the_content', $page->post_content );
+		}
+
+		// Should we replace the title...
+		if ( empty( $page->post_title ) ) {
+
+			// Use the topics archive
+			if ( 'topics' === bbp_show_on_root() ) {
+				$new_title = bbp_get_topic_archive_title();
+
+			// No page so show the archive
+			} else {
+				$new_title = bbp_get_forum_archive_title();
+			}
+
+		// ...or use the existing page title?
+		} else {
+			$new_title = apply_filters( 'the_title',   $page->post_title   );
+		}
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => ! empty( $page->ID ) ? $page->ID : 0,
+			'post_title'     => $new_title,
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $new_content,
+			'post_type'      => bbp_get_forum_post_type(),
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+
+	// Single Forum
+	} elseif ( bbp_is_forum_edit() ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => bbp_get_forum_id(),
+			'post_title'     => bbp_get_forum_title(),
+			'post_author'    => bbp_get_forum_author_id(),
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $bbp_shortcodes->display_forum_form(),
+			'post_type'      => bbp_get_forum_post_type(),
+			'post_status'    => bbp_get_forum_visibility(),
+			'is_single'      => true,
+			'comment_status' => 'closed'
+		) );
+
+		// Lock the forum from other edits
+		bbp_set_post_lock( bbp_get_forum_id() );
+
+	} elseif ( bbp_is_single_forum() ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => bbp_get_forum_id(),
+			'post_title'     => bbp_get_forum_title(),
+			'post_author'    => bbp_get_forum_author_id(),
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $bbp_shortcodes->display_forum( array( 'id' => bbp_get_forum_id() ) ),
+			'post_type'      => bbp_get_forum_post_type(),
+			'post_status'    => bbp_get_forum_visibility(),
+			'is_single'      => true,
+			'comment_status' => 'closed'
+		) );
+
+	/** Topics ************************************************************/
+
+	// Topic archive
+	} elseif ( bbp_is_topic_archive() ) {
+
+		// Page exists where this archive should be
+		$page = bbp_get_page_by_path( bbp_get_topic_archive_slug() );
+
+		// Should we replace the content...
+		if ( empty( $page->post_content ) ) {
+			$new_content = $bbp_shortcodes->display_topic_index();
+
+		// ...or use the existing page content?
+		} else {
+			$new_content = apply_filters( 'the_content', $page->post_content );
+		}
+
+		// Should we replace the title...
+		if ( empty( $page->post_title ) ) {
+			$new_title = bbp_get_topic_archive_title();
+
+		// ...or use the existing page title?
+		} else {
+			$new_title = apply_filters( 'the_title',   $page->post_title   );
+		}
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => ! empty( $page->ID ) ? $page->ID : 0,
+			'post_title'     => bbp_get_topic_archive_title(),
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $new_content,
+			'post_type'      => bbp_get_topic_post_type(),
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+
+	// Single Topic
+	} elseif ( bbp_is_topic_edit() || bbp_is_single_topic() ) {
+
+		// Split
+		if ( bbp_is_topic_split() ) {
+			$new_content = bbp_buffer_template_part( 'form', 'topic-split', false );
+
+		// Merge
+		} elseif ( bbp_is_topic_merge() ) {
+			$new_content = bbp_buffer_template_part( 'form', 'topic-merge', false );
+
+		// Edit
+		} elseif ( bbp_is_topic_edit() ) {
+			$new_content = $bbp_shortcodes->display_topic_form();
+
+			// Lock the topic from other edits
+			bbp_set_post_lock( bbp_get_topic_id() );
+
+		// Single
+		} else {
+			$new_content = $bbp_shortcodes->display_topic( array( 'id' => bbp_get_topic_id() ) );
+		}
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => bbp_get_topic_id(),
+			'post_title'     => bbp_get_topic_title(),
+			'post_author'    => bbp_get_topic_author_id(),
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $new_content,
+			'post_type'      => bbp_get_topic_post_type(),
+			'post_status'    => bbp_get_topic_status(),
+			'is_single'      => true,
+			'comment_status' => 'closed'
+		) );
+
+	/** Replies ***********************************************************/
+
+	// Reply archive
+	} elseif ( is_post_type_archive( bbp_get_reply_post_type() ) ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => esc_html__( 'Replies', 'bbpress' ),
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $bbp_shortcodes->display_reply_index(),
+			'post_type'      => bbp_get_reply_post_type(),
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+
+	// Single Reply
+	} elseif ( bbp_is_reply_edit() || bbp_is_single_reply() ) {
+
+		// Move
+		if ( bbp_is_reply_move() ) {
+			$new_content = bbp_buffer_template_part( 'form', 'reply-move', false );
+
+		// Edit
+		} elseif ( bbp_is_reply_edit() ) {
+			$new_content = $bbp_shortcodes->display_reply_form();
+
+			// Lock the reply from other edits
+			bbp_set_post_lock( bbp_get_reply_id() );
+
+		// Single
+		} else {
+			$new_content = $bbp_shortcodes->display_reply( array( 'id' => get_the_ID() ) );
+		}
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => bbp_get_reply_id(),
+			'post_title'     => bbp_get_reply_title(),
+			'post_author'    => bbp_get_reply_author_id(),
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $new_content,
+			'post_type'      => bbp_get_reply_post_type(),
+			'post_status'    => bbp_get_reply_status(),
+			'is_single'      => true,
+			'comment_status' => 'closed'
+		) );
+
+	/** Views *************************************************************/
+
+	} elseif ( bbp_is_single_view() ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bbp_get_view_title(),
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $bbp_shortcodes->display_view( array( 'id' => get_query_var( bbp_get_view_rewrite_id() ) ) ),
+			'post_type'      => '',
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+
+	/** Search ************************************************************/
+
+	} elseif ( bbp_is_search() ) {
+
+		// Reset post
+		bbp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bbp_get_search_title(),
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $bbp_shortcodes->display_search( array( 'search' => get_query_var( bbp_get_search_rewrite_id() ) ) ),
+			'post_type'      => '',
+			'post_status'    => bbp_get_public_status_id(),
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+
+	/** Topic Tags ********************************************************/
+
+	// Topic Tag Edit
+	} elseif ( bbp_is_topic_tag_edit() || bbp_is_topic_tag() ) {
+
+		// Stash the current term in a new var
+		set_query_var( 'bbp_topic_tag', get_query_var( 'term' ) );
+
+		// Show topics of tag
+		if ( bbp_is_topic_tag() ) {
+			$new_content = $bbp_shortcodes->display_topics_of_tag( array( 'id' => bbp_get_topic_tag_id() ) );
+
+		// Edit topic tag
+		} elseif ( bbp_is_topic_tag_edit() ) {
+			$new_content = $bbp_shortcodes->display_topic_tag_form();
+		}
+
+		// Reset the post with our new title
+		bbp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_author'    => 0,
+			'post_date'      => bbp_get_empty_datetime(),
+			'post_content'   => $new_content,
+			'post_type'      => '',
+			'post_title'     => sprintf( esc_html__( 'Topic Tag: %s', 'bbpress' ), bbp_get_topic_tag_name() ),
+			'post_status'    => bbp_get_public_status_id(),
+			'is_tax'         => true,
+			'is_archive'     => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Bail if the template already matches a bbPress template. This includes
+	 * archive-* and single-* WordPress post_type matches (allowing
+	 * themes to use the expected format) as well as all bbPress-specific
+	 * template files for users, topics, forums, etc...
+	 *
+	 * We do this after the above checks to prevent incorrect 404 body classes
+	 * and header statuses, as well as to set the post global as needed.
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/1478/
+	 */
+	if ( bbp_is_template_included() ) {
+		return $template;
+
+	/**
+	 * If we are relying on the built-in theme compatibility API to load
+	 * the proper content, we need to intercept the_content, replace the
+	 * output, and display ours instead.
+	 *
+	 * To do this, we first remove all filters from 'the_content' and hook
+	 * our own function into it, which runs a series of checks to determine
+	 * the context, and then uses the built in shortcodes to output the
+	 * correct results from inside an output buffer.
+	 *
+	 * Uses bbp_get_theme_compat_templates() to provide fall-backs that
+	 * should be coded without superfluous mark-up and logic (prev/next
+	 * navigation, comments, date/time, etc...)
+	 *
+	 * Hook into the 'bbp_get_bbpress_template' to override the array of
+	 * possible templates, or 'bbp_bbpress_template' to override the result.
+	 */
+	} elseif ( bbp_is_theme_compat_active() ) {
+		bbp_remove_all_filters( 'the_content' );
+
+		$template = bbp_get_theme_compat_templates();
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_template_include_theme_compat', $template );
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Remove the canonical redirect to allow pretty pagination
+ *
+ * @since 2.0.0 bbPress (r2628)
+ *
+ * @param string $redirect_url Redirect url
+ *
+ * @return bool|string False if it's a topic/forum and their first page,
+ *                      otherwise the redirect url
+ */
+function bbp_redirect_canonical( $redirect_url ) {
+
+	// Canonical is for the beautiful
+	if ( bbp_use_pretty_urls() ) {
+
+		// If viewing beyond page 1 of several
+		if ( 1 < bbp_get_paged() ) {
+
+			// Only on single topics...
+			if ( bbp_is_single_topic() ) {
+				$redirect_url = false;
+
+			// ...and single forums...
+			} elseif ( bbp_is_single_forum() ) {
+				$redirect_url = false;
+
+			// ...and single replies...
+			} elseif ( bbp_is_single_reply() ) {
+				$redirect_url = false;
+
+			// ...and any single anything else...
+			//
+			// @todo - Find a more accurate way to disable paged canonicals for
+			//          paged shortcode usage within other posts.
+			} elseif ( is_page() || is_singular() ) {
+				$redirect_url = false;
+			}
+
+		// If editing a topic
+		} elseif ( bbp_is_topic_edit() ) {
+			$redirect_url = false;
+
+		// If editing a reply
+		} elseif ( bbp_is_reply_edit() ) {
+			$redirect_url = false;
+		}
+	}
+
+	return $redirect_url;
+}
+
+/** Filters *******************************************************************/
+
+/**
+ * Removes all filters from a WordPress filter, and stashes them in the $bbp
+ * global in the event they need to be restored later.
+ *
+ * @since 2.0.0 bbPress (r3251)
+ *
+ * @global WP_filter $wp_filter
+ * @global array $merged_filters
+ * @param string $tag
+ * @param int $priority
+ * @return bool
+ */
+function bbp_remove_all_filters( $tag, $priority = false ) {
+	global $wp_filter, $merged_filters;
+
+	$bbp = bbpress();
+
+	// Filters exist
+	if ( isset( $wp_filter[ $tag ] ) ) {
+
+		// Filters exist in this priority
+		if ( ! empty( $priority ) && isset( $wp_filter[ $tag ][ $priority ] ) ) {
+
+			// Store filters in a backup
+			$bbp->filters->wp_filter[ $tag ][ $priority ] = $wp_filter[ $tag ][ $priority ];
+
+			// Unset the filters
+			unset( $wp_filter[ $tag ][ $priority ] );
+
+		// Priority is empty
+		} else {
+
+			// Store filters in a backup
+			$bbp->filters->wp_filter[ $tag ] = $wp_filter[ $tag ];
+
+			// Unset the filters
+			unset( $wp_filter[ $tag ] );
+		}
+	}
+
+	// Check merged filters
+	if ( isset( $merged_filters[ $tag ] ) ) {
+
+		// Store filters in a backup
+		$bbp->filters->merged_filters[ $tag ] = $merged_filters[ $tag ];
+
+		// Unset the filters
+		unset( $merged_filters[ $tag ] );
+	}
+
+	return true;
+}
+
+/**
+ * Restores filters from the $bbp global that were removed using
+ * bbp_remove_all_filters()
+ *
+ * @since 2.0.0 bbPress (r3251)
+ *
+ * @global WP_filter $wp_filter
+ * @global array $merged_filters
+ * @param string $tag
+ * @param int $priority
+ * @return bool
+ */
+function bbp_restore_all_filters( $tag, $priority = false ) {
+	global $wp_filter, $merged_filters;
+
+	$bbp = bbpress();
+
+	// Filters exist
+	if ( isset( $bbp->filters->wp_filter[ $tag ] ) ) {
+
+		// Filters exist in this priority
+		if ( ! empty( $priority ) && isset( $bbp->filters->wp_filter[ $tag ][ $priority  ] ) ) {
+
+			// Store filters in a backup
+			$wp_filter[ $tag ][ $priority ] = $bbp->filters->wp_filter[ $tag ][ $priority ];
+
+			// Unset the filters
+			unset( $bbp->filters->wp_filter[ $tag ][ $priority ] );
+
+		// Priority is empty
+		} else {
+
+			// Store filters in a backup
+			$wp_filter[ $tag ] = $bbp->filters->wp_filter[ $tag ];
+
+			// Unset the filters
+			unset( $bbp->filters->wp_filter[ $tag ] );
+		}
+	}
+
+	// Check merged filters
+	if ( isset( $bbp->filters->merged_filters[ $tag ] ) ) {
+
+		// Store filters in a backup
+		$merged_filters[ $tag ] = $bbp->filters->merged_filters[ $tag ];
+
+		// Unset the filters
+		unset( $bbp->filters->merged_filters[ $tag ] );
+	}
+
+	return true;
+}
+
+/**
+ * Force comments_status to 'closed' for bbPress post types
+ *
+ * @since 2.1.0 bbPress (r3589)
+ *
+ * @param bool $open True if open, false if closed
+ * @param int $post_id ID of the post to check
+ * @return bool True if open, false if closed
+ */
+function bbp_force_comment_status( $open = false, $post_id = 0 ) {
+
+	// Default return value is what is passed in $open
+	$retval = (bool) $open;
+
+	// Get the post type of the post ID
+	$post_type = get_post_type( $post_id );
+
+	// Only force for bbPress post types
+	if ( in_array( $post_type, bbp_get_post_types(), true ) ) {
+		$retval = false;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_force_comment_status', $retval, $open, $post_id, $post_type );
+}
+
+/**
+ * Remove "prev" and "next" relational links from <head> on bbPress pages.
+ *
+ * WordPress automatically generates these relational links to the current
+ * page, but bbPress does not use these links, nor would they work the same.
+ *
+ * In this function, we remove these links when on a bbPress page. This also
+ * prevents additional, unnecessary queries from running.
+ *
+ * @since 2.6.0 bbPress (r7071)
+ */
+function bbp_remove_adjacent_posts() {
+
+	// Bail if not a bbPress page
+	if ( ! is_bbpress() ) {
+		return;
+	}
+
+	// Remove the WordPress core action for adjacent posts
+	remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10 );
+}
Index: /tags/2.6.6/src/includes/core/update.php
===================================================================
--- /tags/2.6.6/src/includes/core/update.php	(revision 7162)
+++ /tags/2.6.6/src/includes/core/update.php	(revision 7162)
@@ -0,0 +1,631 @@
+<?php
+
+/**
+ * bbPress Updater
+ *
+ * @package bbPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * If there is no raw DB version, this is the first installation
+ *
+ * @since 2.1.0 bbPress (r3764)
+ *
+ * @return bool True if update, False if not
+ */
+function bbp_is_install() {
+	return ! bbp_get_db_version_raw();
+}
+
+/**
+ * Compare the bbPress version to the DB version to determine if updating
+ *
+ * @since 2.0.0 bbPress (r3421)
+ *
+ * @return bool True if update, False if not
+ */
+function bbp_is_update() {
+	$raw    = (int) bbp_get_db_version_raw();
+	$cur    = (int) bbp_get_db_version();
+	$retval = (bool) ( $raw < $cur );
+	return $retval;
+}
+
+/**
+ * Determine if bbPress is being activated
+ *
+ * Note that this function currently is not used in bbPress core and is here
+ * for third party plugins to use to check for bbPress activation.
+ *
+ * @since 2.0.0 bbPress (r3421)
+ *
+ * @return bool True if activating bbPress, false if not
+ */
+function bbp_is_activation( $basename = '' ) {
+	global $pagenow;
+
+	$bbp    = bbpress();
+	$action = false;
+
+	// Bail if not in admin/plugins
+	if ( ! ( is_admin() && ( 'plugins.php' === $pagenow ) ) ) {
+		return false;
+	}
+
+	if ( ! empty( $_REQUEST['action'] ) && ( '-1' !== $_REQUEST['action'] ) ) {
+		$action = $_REQUEST['action'];
+	} elseif ( ! empty( $_REQUEST['action2'] ) && ( '-1' !== $_REQUEST['action2'] ) ) {
+		$action = $_REQUEST['action2'];
+	}
+
+	// Bail if not activating
+	if ( empty( $action ) || ! in_array( $action, array( 'activate', 'activate-selected', true ) ) ) {
+		return false;
+	}
+
+	// The plugin(s) being activated
+	if ( $action === 'activate' ) {
+		$plugins = isset( $_GET['plugin'] ) ? array( $_GET['plugin'] ) : array();
+	} else {
+		$plugins = isset( $_POST['checked'] ) ? (array) $_POST['checked'] : array();
+	}
+
+	// Set basename if empty
+	if ( empty( $basename ) && ! empty( $bbp->basename ) ) {
+		$basename = $bbp->basename;
+	}
+
+	// Bail if no basename
+	if ( empty( $basename ) ) {
+		return false;
+	}
+
+	// Is bbPress being activated?
+	return in_array( $basename, $plugins, true );
+}
+
+/**
+ * Determine if bbPress is being deactivated
+ *
+ * @since 2.0.0 bbPress (r3421)
+ *
+ * @return bool True if deactivating bbPress, false if not
+ */
+function bbp_is_deactivation( $basename = '' ) {
+	global $pagenow;
+
+	$bbp    = bbpress();
+	$action = false;
+
+	// Bail if not in admin/plugins
+	if ( ! ( is_admin() && ( 'plugins.php' === $pagenow ) ) ) {
+		return false;
+	}
+
+	if ( ! empty( $_REQUEST['action'] ) && ( '-1' !== $_REQUEST['action'] ) ) {
+		$action = $_REQUEST['action'];
+	} elseif ( ! empty( $_REQUEST['action2'] ) && ( '-1' !== $_REQUEST['action2'] ) ) {
+		$action = $_REQUEST['action2'];
+	}
+
+	// Bail if not deactivating
+	if ( empty( $action ) || ! in_array( $action, array( 'deactivate', 'deactivate-selected' ), true ) ) {
+		return false;
+	}
+
+	// The plugin(s) being deactivated
+	if ( $action === 'deactivate' ) {
+		$plugins = isset( $_GET['plugin'] ) ? array( $_GET['plugin'] ) : array();
+	} else {
+		$plugins = isset( $_POST['checked'] ) ? (array) $_POST['checked'] : array();
+	}
+
+	// Set basename if empty
+	if ( empty( $basename ) && ! empty( $bbp->basename ) ) {
+		$basename = $bbp->basename;
+	}
+
+	// Bail if no basename
+	if ( empty( $basename ) ) {
+		return false;
+	}
+
+	// Is bbPress being deactivated?
+	return in_array( $basename, $plugins, true );
+}
+
+/**
+ * Update the DB to the latest version
+ *
+ * @since 2.0.0 bbPress (r3421)
+ */
+function bbp_version_bump() {
+	update_option( '_bbp_db_version', bbp_get_db_version() );
+}
+
+/**
+ * Setup the bbPress updater
+ *
+ * @since 2.0.0 bbPress (r3419)
+ */
+function bbp_setup_updater() {
+
+	// Bail if no update needed
+	if ( ! bbp_is_update() ) {
+		return;
+	}
+
+	// Call the automated updater
+	bbp_version_updater();
+}
+
+/**
+ * Runs when a new site is created in a multisite network, and bbPress is active
+ * on that site (hooked to `bbp_new_site`)
+ *
+ * @since 2.6.0 bbPress (r6779)
+ */
+function bbp_setup_new_site( $site_id = 0 ) {
+
+	// Look for initial content
+	$created = is_multisite()
+		? get_blog_option( $site_id, '_bbp_flag_initial_content', false )
+		: get_option( '_bbp_flag_initial_content', false );
+
+	// Maybe create the initial content
+	if ( ! empty( $created ) ) {
+		bbp_create_initial_content();
+
+		// Flag initial content as created
+		is_multisite()
+			? update_blog_option( $site_id, '_bbp_flag_initial_content', true )
+			: update_option( '_bbp_flag_initial_content', true );
+	}
+}
+
+/**
+ * Create a default forum, topic, and reply
+ *
+ * @since 2.1.0 bbPress (r3767)
+ *
+ * @param array $args Array of arguments to override default values
+ */
+function bbp_create_initial_content( $args = array() ) {
+
+	// Cannot use bbp_get_current_user_id() during activation process
+	$user_id = get_current_user_id();
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'forum_author'  => $user_id,
+		'forum_parent'  => 0,
+		'forum_status'  => 'publish',
+		'forum_title'   => esc_html__( 'General',            'bbpress' ),
+		'forum_content' => esc_html__( 'General Discussion', 'bbpress' ),
+
+		'topic_author'  => $user_id,
+		'topic_title'   => esc_html__( 'Hello World!',                                  'bbpress' ),
+		'topic_content' => esc_html__( 'This is the very first topic in these forums.', 'bbpress' ),
+
+		'reply_author'  => $user_id,
+		'reply_content' => esc_html__( 'And this is the very first reply.', 'bbpress' ),
+	), 'create_initial_content' );
+
+	// Use the same time for each post
+	$current_time = time();
+	$forum_time   = date( 'Y-m-d H:i:s', $current_time - 60 * 60 * 80 );
+	$topic_time   = date( 'Y-m-d H:i:s', $current_time - 60 * 60 * 60 );
+	$reply_time   = date( 'Y-m-d H:i:s', $current_time - 60 * 60 * 40 );
+
+	// Create the initial forum
+	$forum_id = bbp_insert_forum( array(
+		'post_author'  => $r['forum_author'],
+		'post_parent'  => $r['forum_parent'],
+		'post_status'  => $r['forum_status'],
+		'post_title'   => $r['forum_title'],
+		'post_content' => $r['forum_content'],
+		'post_date'    => $forum_time
+	) );
+
+	// Create the initial topic
+	$topic_id = bbp_insert_topic(
+		array(
+			'post_author'  => $r['topic_author'],
+			'post_parent'  => $forum_id,
+			'post_title'   => $r['topic_title'],
+			'post_content' => $r['topic_content'],
+			'post_date'    => $topic_time,
+		),
+		array(
+			'forum_id'     => $forum_id
+		)
+	);
+
+	// Create the initial reply
+	$reply_id = bbp_insert_reply(
+		array(
+			'post_author'  => $r['reply_author'],
+			'post_parent'  => $topic_id,
+			'post_content' => $r['reply_content'],
+			'post_date'    => $reply_time
+		),
+		array(
+			'forum_id'     => $forum_id,
+			'topic_id'     => $topic_id
+		)
+	);
+
+	return array(
+		'forum_id' => $forum_id,
+		'topic_id' => $topic_id,
+		'reply_id' => $reply_id
+	);
+}
+
+/**
+ * The version updater looks at what the current database version is, and
+ * runs whatever other code is needed.
+ *
+ * This is most-often used when the data schema changes, but should also be used
+ * to correct issues with bbPress meta-data silently on software update.
+ *
+ * @since 2.2.0 bbPress (r4104)
+ */
+function bbp_version_updater() {
+
+	// Get the raw database version
+	$raw_db_version = (int) bbp_get_db_version_raw();
+
+	// Only run updater if previous installation exists
+	if ( ! empty( $raw_db_version ) ) {
+
+		/** 2.0 Branch ********************************************************/
+
+		// 2.0, 2.0.1, 2.0.2, 2.0.3
+		if ( $raw_db_version < 200 ) {
+			// No changes
+		}
+
+		/** 2.1 Branch ********************************************************/
+
+		// 2.1, 2.1.1
+		if ( $raw_db_version < 211 ) {
+
+			/**
+			 * Repair private and hidden forum data
+			 *
+			 * @link https://bbpress.trac.wordpress.org/ticket/1891
+			 */
+			bbp_admin_repair_forum_visibility();
+		}
+
+		/** 2.2 Branch ********************************************************/
+
+		// 2.2.x
+		if ( $raw_db_version < 220 ) {
+
+			// Remove any old bbPress roles
+			bbp_remove_roles();
+
+			// Remove capabilities
+			bbp_remove_caps();
+		}
+
+		/** 2.3 Branch ********************************************************/
+
+		// 2.3.x
+		if ( $raw_db_version < 230 ) {
+			// No changes
+		}
+
+		/** 2.4 Branch ********************************************************/
+
+		// 2.4.x
+		if ( $raw_db_version < 240 ) {
+			// No changes
+		}
+
+		/** 2.5 Branch ********************************************************/
+
+		// 2.5.x
+		if ( $raw_db_version < 250 ) {
+			// No changes
+		}
+
+		/** 2.6 Branch ********************************************************/
+
+		// Smaller installs run the upgrades directly
+		if ( ! bbp_is_large_install() ) {
+
+			/**
+			 * Upgrade user favorites and subscriptions
+			 */
+			if ( $raw_db_version < 261 ) {
+				bbp_admin_upgrade_user_favorites();
+				bbp_admin_upgrade_user_topic_subscriptions();
+				bbp_admin_upgrade_user_forum_subscriptions();
+			}
+
+			/**
+			 * Upgrade user engagements
+			 */
+			if ( $raw_db_version < 262 ) {
+				bbp_admin_upgrade_user_engagements();
+			}
+
+			/**
+			 * Repair forum hidden reply count
+			 */
+			if ( $raw_db_version < 263 ) {
+				bbp_admin_repair_forum_hidden_reply_count();
+			}
+
+		// Large installs require manual intervention
+		} else {
+
+			/**
+			 * Upgrade user favorites and subscriptions
+			 */
+			if ( $raw_db_version < 261 ) {
+				bbp_add_pending_upgrade( 'bbp-user-favorites-move' );
+				bbp_add_pending_upgrade( 'bbp-user-topic-subscriptions-move' );
+				bbp_add_pending_upgrade( 'bbp-user-forum-subscriptions-move' );
+
+				// Set strategy to pre-2.6 on large network
+				update_option( '_bbp_engagements_strategy', 'user' );
+			}
+
+			/**
+			 * Upgrade user engagements
+			 */
+			if ( $raw_db_version < 262 ) {
+				bbp_add_pending_upgrade( 'bbp-user-topic-engagements-move' );
+
+				// Set strategy to pre-2.6 on large network
+				update_option( '_bbp_engagements_strategy', 'user' );
+			}
+
+			/**
+			 * Upgrade user engagements
+			 */
+			if ( $raw_db_version < 263 ) {
+				bbp_add_pending_upgrade( 'bbp-forum-hidden-replies' );
+			}
+		}
+	}
+
+	/** All done! *************************************************************/
+
+	// Bump the version
+	bbp_version_bump();
+
+	// Delete rewrite rules to force a flush
+	bbp_delete_rewrite_rules();
+}
+
+/**
+ * Redirect user to the "What's New" page on activation
+ *
+ * @since 2.2.0 bbPress (r4389)
+ *
+ * @internal Used internally to redirect bbPress to the about page on activation
+ *
+ * @return If network admin or bulk activation
+ */
+function bbp_add_activation_redirect() {
+
+	// Bail if activating from network, or bulk
+	if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
+		return;
+	}
+
+	// Add the redirect trigger
+	update_user_option( get_current_user_id(), '_bbp_activation_redirect', true );
+}
+
+/**
+ * Redirect user to "What's New" page on activation
+ *
+ * @since 2.2.0 bbPress (r4389)
+ *
+ * @internal Used internally to redirect bbPress to the about page on activation
+ *
+ * @return If no transient, or in network admin, or is bulk activation
+ */
+function bbp_do_activation_redirect() {
+
+	// Bail if no redirect trigger
+	if ( ! get_user_option( '_bbp_activation_redirect' ) ) {
+		return;
+	}
+
+	// Delete the redirect trigger
+	delete_user_option( get_current_user_id(), '_bbp_activation_redirect' );
+
+	// Bail if activating from network, or bulk
+	if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
+		return;
+	}
+
+	// Bail if the current user cannot see the about page
+	if ( ! current_user_can( 'bbp_about_page' ) ) {
+		return;
+	}
+
+	// Redirect to bbPress about page
+	bbp_redirect( add_query_arg( array( 'page' => 'bbp-about' ), admin_url( 'index.php' ) ) );
+}
+
+/**
+ * Hooked to the 'bbp_activate' action, this helper function automatically makes
+ * the current user a Key Master in the forums if they just activated bbPress,
+ * regardless of the bbp_allow_global_access() setting.
+ *
+ * @since 2.4.0 bbPress (r4910)
+ *
+ * @internal Used to internally make the current user a keymaster on activation
+ *
+ * @return If user can't activate plugins or is already a keymaster
+ */
+function bbp_make_current_user_keymaster() {
+
+	// Catch all, to prevent premature user initialization
+	if ( ! did_action( 'set_current_user' ) ) {
+		return;
+	}
+
+	// Bail if not logged in or already a member of this site
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	// Bail if the current user can't activate plugins since previous pageload
+	if ( ! current_user_can( 'activate_plugins' ) ) {
+		return;
+	}
+
+	// Cannot use bbp_get_current_user_id() during activation process
+	$user_id = get_current_user_id();
+
+	// Get the current blog ID, to know if they should be promoted here
+	$blog_id = get_current_blog_id();
+
+	// Bail if user is not actually a member of this site
+	if ( ! is_user_member_of_blog( $user_id, $blog_id ) ) {
+		return;
+	}
+
+	// Bail if the current user already has a forum role to prevent
+	// unexpected role and capability escalation.
+	if ( bbp_get_user_role( $user_id ) ) {
+		return;
+	}
+
+	// Make the current user a keymaster
+	bbp_set_user_role( $user_id, bbp_get_keymaster_role() );
+
+	// Reload the current user so caps apply immediately
+	wp_get_current_user();
+}
+
+/** Pending Upgrades **********************************************************/
+
+/**
+ * Return the number of pending upgrades
+ *
+ * @since 2.6.0 bbPress (r6895)
+ *
+ * @param string $type Type of pending upgrades (upgrade|repair|empty)
+ *
+ * @return int
+ */
+function bbp_get_pending_upgrade_count( $type = '' ) {
+	return count( (array) bbp_get_pending_upgrades( $type ) );
+}
+
+/**
+ * Return an array of pending upgrades
+ *
+ * @since 2.6.0 bbPress (r6895)
+ *
+ * @param string $type Type of pending upgrades (upgrade|repair|empty)
+ *
+ * @return array
+ */
+function bbp_get_pending_upgrades( $type = '' ) {
+
+	// Get the pending upgrades
+	$retval = (array) get_option( '_bbp_db_pending_upgrades', array() );
+
+	// Looking for a specific type?
+	if ( ! empty( $type ) ) {
+		$tools   = bbp_get_admin_repair_tools( $type );
+		$plucked = array_keys( wp_list_pluck( $tools, 'type' ) );
+		$retval  = array_intersect( $retval, $plucked );
+	}
+
+	return (array) $retval;
+}
+
+/**
+ * Add an upgrade ID to pending upgrades array
+ *
+ * @since 2.6.0 bbPress (r6895)
+ *
+ * @param string $upgrade_id
+ */
+function bbp_add_pending_upgrade( $upgrade_id = '' ) {
+
+	// Get the pending upgrades option
+	$pending = bbp_get_pending_upgrades();
+
+	// Maybe add upgrade ID to end of pending array
+	if ( false === array_search( $upgrade_id, $pending, true ) ) {
+		array_push( $pending, $upgrade_id );
+	}
+
+	// Update and return
+	return update_option( '_bbp_db_pending_upgrades', $pending );
+}
+
+/**
+ * Add an upgrade ID to pending upgrades array
+ *
+ * @since 2.6.0 bbPress (r6895)
+ *
+ * @param string $upgrade_id
+ */
+function bbp_remove_pending_upgrade( $upgrade_id = '' ) {
+
+	// Get the pending upgrades option
+	$pending = bbp_get_pending_upgrades();
+
+	// Look for this upgrade ID
+	$index = array_search( $upgrade_id, $pending, true );
+
+	// Maybe remove upgrade ID from pending array
+	if ( false !== $index ) {
+		unset( $pending[ $index ] );
+	}
+
+	// Update and return
+	return update_option( '_bbp_db_pending_upgrades', $pending );
+}
+
+/**
+ * Delete all pending upgrades
+ *
+ * @since 2.6.0 bbPress (r6895)
+ */
+function bbp_clear_pending_upgrades() {
+	return delete_option( '_bbp_db_pending_upgrades' );
+}
+
+/**
+ * Maybe append an upgrade count to a string
+ *
+ * @since 2.6.0 bbPress (r6896)
+ *
+ * @param string $string Text to append count to
+ * @param string $type   Type of pending upgrades (upgrade|repair|empty)
+ *
+ * @return string
+ */
+function bbp_maybe_append_pending_upgrade_count( $string = '', $type = '' ) {
+
+	// Look for an upgrade count
+	$count = bbp_get_pending_upgrade_count( $type );
+
+	// Append the count to the string
+	if ( ! empty( $count ) ) {
+		$suffix = ' <span class="awaiting-mod count-' . absint( $count ) . '"><span class="pending-count">' . bbp_number_format( $count ) . '</span></span>';
+		$string = "{$string}{$suffix}";
+	}
+
+	// Return the string, maybe with a count
+	return $string;
+}
Index: /tags/2.6.6/src/includes/extend/akismet.php
===================================================================
--- /tags/2.6.6/src/includes/extend/akismet.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/akismet.php	(revision 7162)
@@ -0,0 +1,892 @@
+<?php
+
+/**
+ * Main bbPress Akismet Class
+ *
+ * @package bbPress
+ * @subpackage Akismet
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Akismet' ) ) :
+/**
+ * Loads Akismet extension
+ *
+ * @since 2.0.0 bbPress (r3277)
+ *
+ * @package bbPress
+ * @subpackage Akismet
+ */
+class BBP_Akismet {
+
+	/**
+	 * The main bbPress Akismet loader
+	 *
+	 * @since 2.0.0 bbPress (r3277)
+	 */
+	public function __construct() {
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the admin hooks
+	 *
+	 * @since 2.0.0 bbPress (r3277)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Prevent debug notices
+		$checks = array();
+
+		// bbPress functions to check for spam
+		$checks['check']  = array(
+			'bbp_new_topic_pre_insert'  => 1,  // New topic check
+			'bbp_new_reply_pre_insert'  => 1,  // New reply check
+			'bbp_edit_topic_pre_insert' => 1,  // Edit topic check
+			'bbp_edit_reply_pre_insert' => 1   // Edit reply check
+		);
+
+		// bbPress functions for spam and ham submissions
+		$checks['submit'] = array(
+			'bbp_spammed_topic'   => 10, // Spammed topic
+			'bbp_unspammed_topic' => 10, // Unspammed reply
+			'bbp_spammed_reply'   => 10, // Spammed reply
+			'bbp_unspammed_reply' => 10, // Unspammed reply
+		);
+
+		// Add the checks
+		foreach ( $checks as $type => $functions ) {
+			foreach ( $functions as $function => $priority ) {
+				add_filter( $function, array( $this, $type . '_post'  ), $priority );
+			}
+		}
+
+		// Update post meta
+		add_action( 'wp_insert_post', array( $this, 'update_post_meta' ), 10, 2 );
+
+		// Admin
+		if ( is_admin() ) {
+			add_action( 'add_meta_boxes', array( $this, 'add_metaboxes' ) );
+		}
+	}
+
+	/**
+	 * Converts topic/reply data into Akismet comment checking format
+	 *
+	 * @since 2.0.0 bbPress (r3277)
+	 *
+	 * @param array $post_data
+	 *
+	 * @return array Array of post data
+	 */
+	public function check_post( $post_data = array() ) {
+
+		// Define local variables
+		$user_data = array();
+		$post_permalink = '';
+
+		// Post is not published
+		if ( bbp_get_public_status_id() !== $post_data['post_status'] ) {
+			return $post_data;
+		}
+
+		// Cast the post_author to 0 if it's empty
+		if ( empty( $post_data['post_author'] ) ) {
+			$post_data['post_author'] = 0;
+		}
+
+		/** Author ************************************************************/
+
+		$user_data['last_active'] = '';
+		$user_data['registered']  = date( 'Y-m-d H:i:s');
+		$user_data['total_posts'] = (int) bbp_get_user_post_count( $post_data['post_author'] );
+
+		// Get user data
+		$userdata       = get_userdata( $post_data['post_author'] );
+		$anonymous_data = bbp_filter_anonymous_post_data();
+
+		// Author is anonymous
+		if ( ! bbp_has_errors() ) {
+			$user_data['name']    = $anonymous_data['bbp_anonymous_name'];
+			$user_data['email']   = $anonymous_data['bbp_anonymous_email'];
+			$user_data['website'] = $anonymous_data['bbp_anonymous_website'];
+
+		// Author is logged in
+		} elseif ( ! empty( $userdata ) ) {
+			$user_data['name']       = $userdata->display_name;
+			$user_data['email']      = $userdata->user_email;
+			$user_data['website']    = $userdata->user_url;
+			$user_data['registered'] = $userdata->user_registered;
+
+		// Missing author data, so set some empty strings
+		} else {
+			$user_data['name']    = '';
+			$user_data['email']   = '';
+			$user_data['website'] = '';
+		}
+
+		/** Post **************************************************************/
+
+		if ( ! empty( $post_data['post_parent'] ) ) {
+
+			// Use post parent for permalink
+			$post_permalink = get_permalink( $post_data['post_parent'] );
+
+			// Use post parent to get datetime of last reply on this topic
+			$reply_id = bbp_get_topic_last_reply_id( $post_data['post_parent'] );
+			if ( ! empty( $reply_id ) ) {
+				$user_data['last_active'] = get_post_field( 'post_date', $reply_id );
+			}
+		}
+
+		// Pass title & content together into comment content
+		$_post_content = trim( $post_data['post_title'] . "\n\n" . $post_data['post_content'] );
+
+		// Check if the post data is spammy...
+		$_post = $this->maybe_spam( array(
+			'comment_author'                 => $user_data['name'],
+			'comment_author_email'           => $user_data['email'],
+			'comment_author_url'             => $user_data['website'],
+			'comment_content'                => $_post_content,
+			'comment_post_ID'                => $post_data['post_parent'],
+			'comment_type'                   => $post_data['post_type'],
+			'comment_total'                  => $user_data['total_posts'],
+			'comment_last_active_gmt'        => $user_data['last_active'],
+			'comment_account_registered_gmt' => $user_data['registered'],
+			'permalink'                      => $post_permalink,
+			'referrer'                       => wp_get_raw_referer(),
+			'user_agent'                     => bbp_current_author_ua(),
+			'user_ID'                        => $post_data['post_author'],
+			'user_ip'                        => bbp_current_author_ip(),
+			'user_role'                      => $this->get_user_roles( $post_data['post_author'] ),
+		) );
+
+		// Set the result headers (from maybe_spam() above)
+		$post_data['bbp_akismet_result_headers'] = ! empty( $_post['bbp_akismet_result_headers'] )
+			? $_post['bbp_akismet_result_headers'] // raw
+			: esc_html__( 'No response', 'bbpress' );
+
+		// Set the result (from maybe_spam() above)
+		$post_data['bbp_akismet_result'] = ! empty( $_post['bbp_akismet_result'] )
+			? $_post['bbp_akismet_result'] // raw
+			: esc_html__( 'No response', 'bbpress' );
+
+		// Avoid recurrsion by unsetting results
+		unset(
+			$_post['bbp_akismet_result_headers'],
+			$_post['bbp_akismet_result']
+		);
+		$post_data['bbp_post_as_submitted'] = $_post;
+
+		// Cleanup to avoid touching this variable again below
+		unset( $_post );
+
+		// Allow post_data to be manipulated
+		$post_data = apply_filters( 'bbp_akismet_check_post', $post_data );
+
+		// Parse and log the last response
+		$this->last_post = $this->parse_response( $post_data );
+
+		// Return the last response back to the filter
+		return $this->last_post;
+	}
+
+	/**
+	 * Parse the response from the Akismet service, and alter the post data as
+	 * necessary. For example, switch the status to `spam` if spammy.
+	 *
+	 * Note: this method also skiis responsible for allowing users who can moderate, to
+	 * never have their posts marked as spam. This is because they are "trusted"
+	 * users. However, their posts are still sent to Akismet to be checked.
+	 *
+	 * @since 2.6.0 bbPress (r6873)
+	 *
+	 * @param array $post_data
+	 *
+	 * @return array
+	 */
+	private function parse_response( $post_data = array() ) {
+
+		// Get the parent ID of the post as submitted
+		$parent_id = ! empty( $post_data['bbp_post_as_submitted']['comment_post_ID'] )
+			? absint( $post_data['bbp_post_as_submitted']['comment_post_ID'] )
+			: 0;
+
+		// Allow moderators to skip spam (includes per-forum moderators via $parent_id)
+		$skip_spam = current_user_can( 'moderate', $parent_id );
+
+		// Bail early if current user can skip spam enforcement
+		if ( apply_filters( 'bbp_bypass_spam_enforcement', $skip_spam, $post_data ) ) {
+			return $post_data;
+		}
+
+		// Result is spam, so set the status as such
+		if ( 'true' === $post_data['bbp_akismet_result'] ) {
+
+			// Let plugins do their thing
+			do_action( 'bbp_akismet_spam_caught' );
+
+			// Set post_status to spam
+			$post_data['post_status'] = bbp_get_spam_status_id();
+
+			// Filter spammy tags into meta data
+			add_filter( 'bbp_new_reply_pre_set_terms', array( $this, 'filter_post_terms' ), 1, 3 );
+		}
+
+		// Return the (potentially modified) post data
+		return $post_data;
+	}
+
+	/**
+	 * Submit a post for spamming or hamming
+	 *
+	 * @since 2.0.0 bbPress (r3277)
+	 *
+	 * @param int $post_id
+	 *
+	 * @global string $akismet_api_host
+	 * @global string $akismet_api_port
+	 * @global object $current_user
+	 * @global object $current_site
+	 *
+	 * @return array Array of existing topic terms
+	 */
+	public function submit_post( $post_id = 0 ) {
+		global $current_user, $current_site;
+
+		// Innocent until proven guilty
+		$request_type   = 'ham';
+		$current_filter = current_filter();
+
+		// Check this filter and adjust the $request_type accordingly
+		switch ( $current_filter ) {
+
+			// Mysterious, and straight from the can
+			case 'bbp_spammed_topic' :
+			case 'bbp_spammed_reply' :
+				$request_type = 'spam';
+				break;
+
+			// Honey-glazed, a straight off the bone
+			case 'bbp_unspammed_topic' :
+			case 'bbp_unspammed_reply' :
+				$request_type = 'ham';
+				break;
+
+			// Possibly poison...
+			default :
+				return;
+		}
+
+		// Setup some variables
+		$post_id = (int) $post_id;
+
+		// Make sure we have a post
+		$_post = get_post( $post_id );
+
+		// Bail if get_post() fails
+		if ( empty( $_post ) ) {
+			return;
+		}
+
+		// Bail if we're spamming, but the post_status isn't spam
+		if ( ( 'spam' === $request_type ) && ( bbp_get_spam_status_id() !== $_post->post_status ) ) {
+			return;
+		}
+
+		// Pass title & content together into comment content
+		$_post_content = trim( $_post->post_title . "\n\n" . $_post->post_content );
+
+		// Set some default post_data
+		$post_data = array(
+			'comment_approved'     => $_post->post_status,
+			'comment_author'       => $_post->post_author ? get_the_author_meta( 'display_name', $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_name',    true ),
+			'comment_author_email' => $_post->post_author ? get_the_author_meta( 'email',        $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_email',   true ),
+			'comment_author_url'   => $_post->post_author ? bbp_get_user_profile_url(            $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_website', true ),
+			'comment_content'      => $_post_content,
+			'comment_date_gmt'     => $_post->post_date_gmt,
+			'comment_ID'           => $post_id,
+			'comment_post_ID'      => $_post->post_parent,
+			'comment_type'         => $_post->post_type,
+			'permalink'            => get_permalink( $post_id ),
+			'user_ID'              => $_post->post_author,
+			'user_ip'              => get_post_meta( $post_id, '_bbp_author_ip', true ),
+			'user_role'            => $this->get_user_roles( $_post->post_author ),
+		);
+
+		// Use the original version stored in post_meta if available
+		$as_submitted = get_post_meta( $post_id, '_bbp_akismet_as_submitted', true );
+		if ( $as_submitted && is_array( $as_submitted ) && isset( $as_submitted['comment_content'] ) ) {
+			$post_data = array_merge( $post_data, $as_submitted );
+		}
+
+		// Add the reporter IP address
+		$post_data['reporter_ip']  = bbp_current_author_ip();
+
+		// Add some reporter info
+		if ( is_object( $current_user ) ) {
+			$post_data['reporter'] = $current_user->user_login;
+		}
+
+		// Add the current site domain
+		if ( is_object( $current_site ) ) {
+			$post_data['site_domain'] = $current_site->domain;
+		}
+
+		// Place your slide beneath the microscope
+		$post_data = $this->maybe_spam( $post_data, 'submit', $request_type );
+
+		// Manual user action
+		if ( isset( $post_data['reporter'] ) ) {
+
+			// What kind of action
+			switch ( $request_type ) {
+
+				// Spammy
+				case 'spam' :
+					if ( 'topic' === $post_data['comment_type'] ) {
+						/* translators: %s: reporter name */
+						$message = sprintf( esc_html__( '%s reported this topic as spam', 'bbpress' ),
+							$post_data['reporter']
+						);
+					} elseif ( 'reply' === $post_data['comment_type'] ) {
+						/* translators: %s: reporter name */
+						$message = sprintf( esc_html__( '%s reported this reply as spam', 'bbpress' ),
+							$post_data['reporter']
+						);
+					} else {
+						/* translators: 1: reporter name, 2: comment type */
+						$message = sprintf( esc_html__( '%1$s reported this %2$s as spam', 'bbpress' ),
+							$post_data['reporter'],
+							$post_data['comment_type']
+						);
+					}
+
+					$this->update_post_history( $post_id, $message, 'report-spam' );
+					update_post_meta( $post_id, '_bbp_akismet_user_result', 'true'                 );
+					update_post_meta( $post_id, '_bbp_akismet_user',        $post_data['reporter'] );
+					break;
+
+				// Hammy
+				case 'ham' :
+					if ( 'topic' === $post_data['comment_type'] ) {
+						/* translators: %s: reporter name */
+						$message = sprintf( esc_html__( '%s reported this topic as not spam', 'bbpress' ),
+							$post_data['reporter']
+						);
+					} elseif ( 'reply' === $post_data['comment_type'] ) {
+						/* translators: %s: reporter name */
+						$message = sprintf( esc_html__( '%s reported this reply as not spam', 'bbpress' ),
+							$post_data['reporter']
+						);
+					} else {
+						/* translators: 1: reporter name, 2: comment type */
+						$message = sprintf( esc_html__( '%1$s reported this %2$s as not spam', 'bbpress' ),
+							$post_data['reporter'],
+							$post_data['comment_type']
+						);
+					}
+
+					$this->update_post_history( $post_id, $message, 'report-ham' );
+					update_post_meta( $post_id, '_bbp_akismet_user_result', 'false'                 );
+					update_post_meta( $post_id, '_bbp_akismet_user',         $post_data['reporter'] );
+
+					// @todo Topic term revision history
+					break;
+
+				// Possible other actions
+				default :
+					break;
+			}
+		}
+
+		do_action( 'bbp_akismet_submit_' . $request_type . '_post', $post_id, $post_data['bbp_akismet_result'] );
+	}
+
+	/**
+	 * Ping Akismet service and check for spam/ham response
+	 *
+	 * @since 2.0.0 bbPress (r3277)
+	 *
+	 * @param array $post_data
+	 * @param string $check Accepts check|submit
+	 * @param string $spam Accepts spam|ham
+	 *
+	 * @global string $akismet_api_host
+	 * @global string $akismet_api_port
+	 *
+	 * @return array Array of post data
+	 */
+	private function maybe_spam( $post_data = array(), $check = 'check', $spam = 'spam' ) {
+		global $akismet_api_host, $akismet_api_port;
+
+		// Define variables
+		$query_string = $path = $response = '';
+
+		// Make sure post data is an array
+		if ( ! is_array( $post_data ) ) {
+			$post_data = array();
+		}
+
+		// Populate post data
+		$post_data['blog']         = get_option( 'home' );
+		$post_data['blog_charset'] = get_option( 'blog_charset' );
+		$post_data['blog_lang']    = get_locale();
+		$post_data['referrer']     = wp_get_raw_referer();
+		$post_data['user_agent']   = bbp_current_author_ua();
+
+		// Loop through _POST args and rekey strings
+		if ( ! empty( $_POST ) && is_countable( $_POST ) ) {
+			foreach ( $_POST as $key => $value ) {
+				if ( is_string( $value ) ) {
+					$post_data['POST_' . $key] = $value;
+				}
+			}
+		}
+
+		// Loop through _SERVER args and remove allowed keys
+		if ( ! empty( $_SERVER ) && is_countable( $_SERVER ) ) {
+
+			// Keys to ignore
+			$ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );
+
+			foreach ( $_SERVER as $key => $value ) {
+
+				// Key should not be ignored
+				if ( ! in_array( $key, $ignore, true ) && is_string( $value ) ) {
+					$post_data[ $key ] = $value;
+
+				// Key should be ignored
+				} else {
+					$post_data[ $key ] = '';
+				}
+			}
+		}
+
+		// Encode post data
+		if ( ! empty( $post_data ) && is_countable( $post_data ) ) {
+			foreach ( $post_data as $key => $data ) {
+				$query_string .= $key . '=' . urlencode( wp_unslash( $data ) ) . '&';
+			}
+		}
+
+		// Setup the API route
+		if ( 'check' === $check ) {
+			$path = '/1.1/comment-check';
+		} elseif ( 'submit' === $check ) {
+			$path = '/1.1/submit-' . $spam;
+		}
+
+		// Send data to Akismet
+		$response = ! apply_filters( 'bbp_bypass_check_for_spam', false, $post_data )
+			? $this->http_post( $query_string, $akismet_api_host, $path, $akismet_api_port )
+			: false;
+
+		// Set the result headers
+		$post_data['bbp_akismet_result_headers'] = ! empty( $response[0] )
+			? $response[0]
+			: esc_html__( 'No response', 'bbpress' );
+
+		// Set the result
+		$post_data['bbp_akismet_result'] = ! empty( $response[1] )
+			? $response[1]
+			: esc_html__( 'No response', 'bbpress' );
+
+		// Return the post data, with the results of the external Akismet request
+		return $post_data;
+	}
+
+	/**
+	 * Update post meta after a spam check
+	 *
+	 * @since 2.0.0 bbPress (r3308)
+	 *
+	 * @param int $post_id
+	 * @param object $_post
+	 *
+	 * @global object $this->last_post
+	 */
+	public function update_post_meta( $post_id = 0, $_post = false ) {
+
+		// Define local variable(s)
+		$as_submitted = false;
+
+		// Setup some variables
+		$post_id = (int) $post_id;
+
+		// Ensure we have a post object
+		if ( empty( $_post ) ) {
+			$_post = get_post( $post_id );
+		}
+
+		// Set up Akismet last post data
+		if ( ! empty( $this->last_post['bbp_post_as_submitted'] ) ) {
+			$as_submitted = $this->last_post['bbp_post_as_submitted'];
+		}
+
+		// wp_insert_post() might be called in other contexts. Ensure this is
+		// the same topic/reply as was checked by BBP_Akismet::check_post()
+		if ( is_object( $_post ) && ! empty( $this->last_post ) && is_array( $as_submitted ) ) {
+
+			// Get user data
+			$userdata       = get_userdata( $_post->post_author );
+			$anonymous_data = bbp_filter_anonymous_post_data();
+
+			// Which name?
+			$name = ! empty( $anonymous_data['bbp_anonymous_name'] )
+				? $anonymous_data['bbp_anonymous_name']
+				: $userdata->display_name;
+
+			// Which email?
+			$email = ! empty( $anonymous_data['bbp_anonymous_email'] )
+				? $anonymous_data['bbp_anonymous_email']
+				: $userdata->user_email;
+
+			// More checks
+			if (
+
+				// Post matches
+				( intval( $as_submitted['comment_post_ID'] ) === intval( $_post->post_parent ) )
+
+				&&
+
+				// Name matches
+				( $as_submitted['comment_author'] === $name )
+
+				&&
+
+				// Email matches
+				( $as_submitted['comment_author_email'] === $email )
+			) {
+
+				// Normal result: true
+				if ( ! empty( $this->last_post['bbp_akismet_result'] ) && ( $this->last_post['bbp_akismet_result'] === 'true' ) ) {
+
+					// Leave a trail so other's know what we did
+					update_post_meta( $post_id, '_bbp_akismet_result', 'true' );
+					$this->update_post_history(
+						$post_id,
+						esc_html__( 'Akismet caught this post as spam', 'bbpress' ),
+						'check-spam'
+					);
+
+					// If post_status isn't the spam status, as expected, leave a note
+					if ( bbp_get_spam_status_id() !== $_post->post_status ) {
+						$this->update_post_history(
+							$post_id,
+							sprintf(
+								esc_html__( 'Post status was changed to %s', 'bbpress' ),
+								$_post->post_status
+							),
+							'status-changed-' . $_post->post_status
+						);
+					}
+
+				// Normal result: false
+				} elseif ( ! empty( $this->last_post['bbp_akismet_result'] ) && ( $this->last_post['bbp_akismet_result'] === 'false' ) ) {
+
+					// Leave a trail so other's know what we did
+					update_post_meta( $post_id, '_bbp_akismet_result', 'false' );
+					$this->update_post_history(
+						$post_id,
+						esc_html__( 'Akismet cleared this post as not spam', 'bbpress' ),
+						'check-ham'
+					);
+
+					// If post_status is the spam status, which isn't expected, leave a note
+					if ( bbp_get_spam_status_id() === $_post->post_status ) {
+						$this->update_post_history(
+							$post_id,
+							sprintf(
+								esc_html__( 'Post status was changed to %s', 'bbpress' ),
+								$_post->post_status
+							),
+							'status-changed-' . $_post->post_status
+						);
+					}
+
+				// Abnormal result: error
+				} else {
+					// Leave a trail so other's know what we did
+					update_post_meta( $post_id, '_bbp_akismet_error', time() );
+					$this->update_post_history(
+						$post_id,
+						sprintf(
+							esc_html__( 'Akismet was unable to check this post (response: %s), will automatically retry again later.', 'bbpress' ),
+							$this->last_post['bbp_akismet_result']
+						),
+						'check-error'
+					);
+				}
+
+				// Record the complete original data as submitted for checking
+				if ( isset( $this->last_post['bbp_post_as_submitted'] ) ) {
+					update_post_meta(
+						$post_id,
+						'_bbp_akismet_as_submitted',
+						$this->last_post['bbp_post_as_submitted']
+					);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Update Akismet history of a Post
+	 *
+	 * @since 2.0.0 bbPress (r3308)
+	 *
+	 * @param int $post_id
+	 * @param string $message
+	 * @param string $event
+	 */
+	private function update_post_history( $post_id = 0, $message = null, $event = null ) {
+
+		// Define local variable(s)
+		$user = '';
+
+		// Get the current user
+		$current_user = wp_get_current_user();
+
+		// Get the user's login name if possible
+		if ( is_object( $current_user ) && isset( $current_user->user_login ) ) {
+			$user = $current_user->user_login;
+		}
+
+		// This used to be akismet_microtime() but it was removed in 3.0
+		$mtime        = explode( ' ', microtime() );
+		$message_time = $mtime[1] + $mtime[0];
+
+		// Setup the event to be saved
+		$event = array(
+			'time'    => $message_time,
+			'message' => $message,
+			'event'   => $event,
+			'user'    => $user,
+		);
+
+		// Save the event data
+		add_post_meta( $post_id, '_bbp_akismet_history', $event );
+	}
+
+	/**
+	 * Get the Akismet history of a Post
+	 *
+	 * @since 2.0.0 bbPress (r3308)
+	 *
+	 * @param int $post_id
+	 *
+	 * @return array Array of Akismet history
+	 */
+	public function get_post_history( $post_id = 0 ) {
+
+		// Retrieve any previous history
+		$history = get_post_meta( $post_id, '_bbp_akismet_history' );
+
+		// Sort it by the time recorded
+		usort( $history, 'akismet_cmp_time' );
+
+		return $history;
+	}
+
+	/**
+	 * Handle any terms submitted with a post flagged as spam
+	 *
+	 * @since 2.0.0 bbPress (r3308)
+	 *
+	 * @param string $terms Comma-separated list of terms
+	 * @param int $topic_id
+	 * @param int $reply_id
+	 *
+	 * @return array Array of existing topic terms
+	 */
+	public function filter_post_terms( $terms = '', $topic_id = 0, $reply_id = 0 ) {
+
+		// Validate the reply_id and topic_id
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Get any pre-existing terms
+		$existing_terms = bbp_get_topic_tag_names( $topic_id );
+
+		// Save the terms for later in case the reply gets hammed
+		if ( ! empty( $terms ) ) {
+			update_post_meta( $reply_id, '_bbp_akismet_spam_terms', $terms );
+		}
+
+		// Keep the topic tags the same for now
+		return $existing_terms;
+	}
+
+	/**
+	 * Submit data to Akismet service with unique bbPress User Agent
+	 *
+	 * This code is directly taken from the akismet_http_post() function and
+	 * documented to bbPress 2.0 standard.
+	 *
+	 * @since 2.0.0 bbPress (r3466)
+	 *
+	 * @param string $request The request we are sending
+	 * @param string $host The host to send our request to
+	 * @param string $path The path from the host
+	 * @param string $port The port to use
+	 * @param string $ip Optional Override $host with an IP address
+	 * @return mixed WP_Error on error, array on success, empty on failure
+	 */
+	private function http_post( $request, $host, $path, $port = 80, $ip = '' ) {
+
+		// Preload required variables
+		$bbp_version  = bbp_get_version();
+		$http_host    = $host;
+		$blog_charset = get_option( 'blog_charset' );
+		$response     = '';
+
+		// Untque User Agent
+		$akismet_ua     = "bbPress/{$bbp_version} | ";
+		$akismet_ua    .= 'Akismet/' . constant( 'AKISMET_VERSION' );
+
+		// Use specific IP (if provided)
+		if ( ! empty( $ip ) && long2ip( ip2long( $ip ) ) ) {
+			$http_host = $ip;
+		}
+
+		// Setup the arguments
+		$http_args = array(
+			'body'             => $request,
+			'headers'          => array(
+				'Content-Type' => 'application/x-www-form-urlencoded; charset=' . $blog_charset,
+				'Host'         => $host,
+				'User-Agent'   => $akismet_ua
+			),
+			'httpversion'      => '1.0',
+			'timeout'          => 15
+		);
+
+		// Where we are sending our request
+		$akismet_url = 'http://' . $http_host . $path;
+
+		// Send the request
+		$response    = wp_remote_post( $akismet_url, $http_args );
+
+		// Bail if the response is an error
+		if ( is_wp_error( $response ) ) {
+			return '';
+		}
+
+		// No errors so return response
+		return array( $response['headers'], $response['body'] );
+	}
+
+	/**
+	 * Return a user's roles on this site (including super_admin)
+	 *
+	 * @since 2.3.0 bbPress (r4812)
+	 *
+	 * @param int $user_id
+	 *
+	 * @return boolean
+	 */
+	private function get_user_roles( $user_id = 0 ) {
+
+		// Default return value
+		$roles = array();
+
+		// Bail if cannot query the user
+		if ( ! class_exists( 'WP_User' ) || empty( $user_id ) ) {
+			return false;
+		}
+
+		// User ID
+		$user = new WP_User( $user_id );
+		if ( isset( $user->roles ) ) {
+			$roles = (array) $user->roles;
+		}
+
+		// Super admin
+		if ( is_multisite() && is_super_admin( $user_id ) ) {
+			$roles[] = 'super_admin';
+		}
+
+		return implode( ',', $roles );
+	}
+
+	/** Admin *****************************************************************/
+
+	/**
+	 * Add Aksimet History meta-boxes to topics and replies
+	 *
+	 * @since 2.4.0 bbPress (r5049)
+	 */
+	public function add_metaboxes() {
+
+		// Topics
+		add_meta_box(
+			'bbp_akismet_topic_history',
+			__( 'Akismet History', 'bbpress' ),
+			array( $this, 'history_metabox' ),
+			bbp_get_topic_post_type(),
+			'normal',
+			'core'
+		);
+
+		// Replies
+		add_meta_box(
+			'bbp_akismet_reply_history',
+			__( 'Akismet History', 'bbpress' ),
+			array( $this, 'history_metabox' ),
+			bbp_get_reply_post_type(),
+			'normal',
+			'core'
+		);
+	}
+
+	/**
+	 * Output for Akismet History meta-box
+	 *
+	 * @since 2.4.0 bbPress (r5049)
+	 */
+	public function history_metabox() {
+
+		// Post ID
+		$history = $this->get_post_history( get_the_ID() ); ?>
+
+		<div class="akismet-history" style="margin: 13px 0;">
+
+			<?php if ( ! empty( $history ) ) : ?>
+
+				<table>
+					<tbody>
+
+						<?php foreach ( $history as $row ) : ?>
+
+							<tr>
+								<td style="color: #999; text-align: right; white-space: nowrap;">
+									<span title="<?php echo esc_attr( date( 'D d M Y @ h:i:m a', $row['time'] ) . ' GMT' ); ?>">
+										<?php bbp_time_since( $row['time'], false, true ); ?>
+									</span>
+								</td>
+								<td style="padding-left: 5px;">
+									<?php echo esc_html( $row['message'] ); ?>
+								</td>
+							</tr>
+
+						<?php endforeach; ?>
+					</tbody>
+				</table>
+
+			<?php else : ?>
+
+				<p><?php esc_html_e( 'No recorded history. Akismet has not checked this post.', 'bbpress' ); ?></p>
+
+			<?php endif; ?>
+
+		</div>
+
+		<?php
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/extend/buddypress/activity.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/activity.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/activity.php	(revision 7162)
@@ -0,0 +1,670 @@
+<?php
+
+/**
+ * bbPress BuddyPress Activity Class
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_BuddyPress_Activity' ) ) :
+/**
+ * Loads BuddyPress Activity extension
+ *
+ * @since 2.0.0 bbPress (r3395)
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+class BBP_BuddyPress_Activity {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * The name of the BuddyPress component, used in activity streams
+	 *
+	 * @var string
+	 */
+	private $component = '';
+
+	/**
+	 * Forum Create Activity Action
+	 *
+	 * @var string
+	 */
+	private $forum_create = '';
+
+	/**
+	 * Topic Create Activity Action
+	 *
+	 * @var string
+	 */
+	private $topic_create = '';
+
+	/**
+	 * Topic Close Activity Action
+	 *
+	 * @var string
+	 */
+	private $topic_close = '';
+
+	/**
+	 * Topic Edit Activity Action
+	 *
+	 * @var string
+	 */
+	private $topic_edit = '';
+
+	/**
+	 * Topic Open Activity Action
+	 *
+	 * @var string
+	 */
+	private $topic_open = '';
+
+	/**
+	 * Reply Create Activity Action
+	 *
+	 * @var string
+	 */
+	private $reply_create = '';
+
+	/**
+	 * Reply Edit Activity Action
+	 *
+	 * @var string
+	 */
+	private $reply_edit = '';
+
+	/** Setup Methods *********************************************************/
+
+	/**
+	 * The bbPress BuddyPress Activity loader
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+		$this->setup_filters();
+		$this->fully_loaded();
+	}
+
+	/**
+	 * Extension variables
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @access private
+	 */
+	private function setup_globals() {
+
+		// The name of the BuddyPress component, used in activity streams
+		$this->component    = 'bbpress';
+
+		// Forums
+		$this->forum_create = 'bbp_forum_create';
+
+		// Topics
+		$this->topic_create = 'bbp_topic_create';
+		$this->topic_edit   = 'bbp_topic_edit';
+		$this->topic_close  = 'bbp_topic_close';
+		$this->topic_open   = 'bbp_topic_open';
+
+		// Replies
+		$this->reply_create = 'bbp_reply_create';
+		$this->reply_edit   = 'bbp_reply_edit';
+	}
+
+	/**
+	 * Setup the actions
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Register the activity stream actions
+		add_action( 'bp_register_activity_actions',      array( $this, 'register_activity_actions' )        );
+
+		// Hook into topic and reply creation
+		add_action( 'bbp_new_topic',                     array( $this, 'topic_create'              ), 10, 4 );
+		add_action( 'bbp_new_reply',                     array( $this, 'reply_create'              ), 10, 5 );
+
+		// Hook into topic and reply status changes
+		add_action( 'edit_post',                         array( $this, 'topic_update'              ), 10, 2 );
+		add_action( 'edit_post',                         array( $this, 'reply_update'              ), 10, 2 );
+
+		// Hook into topic and reply deletion
+		add_action( 'bbp_delete_topic',                  array( $this, 'topic_delete'              ), 10, 1 );
+		add_action( 'bbp_delete_reply',                  array( $this, 'reply_delete'              ), 10, 1 );
+
+		// Append forum filters in site wide activity streams
+		add_action( 'bp_activity_filter_options',        array( $this, 'activity_filter_options'   ), 10    );
+
+		// Append forum filters in single member activity streams
+		add_action( 'bp_member_activity_filter_options', array( $this, 'activity_filter_options'   ), 10    );
+
+		// Append forum filters in single group activity streams
+		add_action( 'bp_group_activity_filter_options',  array( $this, 'activity_filter_options'   ), 10    );
+	}
+
+	/**
+	 * Setup the filters
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @access private
+	 */
+	private function setup_filters() {
+
+		// Obey BuddyPress commenting rules
+		add_filter( 'bp_activity_can_comment',   array( $this, 'activity_can_comment'   )        );
+
+		// Link directly to the topic or reply
+		add_filter( 'bp_activity_get_permalink', array( $this, 'activity_get_permalink' ), 10, 2 );
+	}
+
+	/**
+	 * Allow the variables, actions, and filters to be modified by third party
+	 * plugins and themes.
+	 *
+	 * @since 2.1.0 bbPress (r3902)
+	 */
+	private function fully_loaded() {
+		do_action_ref_array( 'bbp_buddypress_activity_loaded', array( $this ) );
+	}
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Register our activity actions with BuddyPress
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 */
+	public function register_activity_actions() {
+
+		// Sitewide topic
+		bp_activity_set_action(
+			$this->component,
+			$this->topic_create,
+			esc_html__( 'New forum topic', 'bbpress' ),
+			'bbp_format_activity_action_new_topic',
+			esc_html__( 'Topics', 'bbpress' ),
+			array( 'activity', 'member', 'member_groups', 'group' )
+		);
+
+		// Sitewide reply
+		bp_activity_set_action(
+			$this->component,
+			$this->reply_create,
+			esc_html__( 'New forum reply', 'bbpress' ),
+			'bbp_format_activity_action_new_reply',
+			esc_html__( 'Replies', 'bbpress' ),
+			array( 'activity', 'member', 'member_groups', 'group' )
+		);
+
+		// Group forums
+		/**
+		 * An upstream improvement to BuddyPress is required to make these work,
+		 * but they are included here to make our lives easier later.
+		if ( bp_is_active( 'groups' ) ) {
+
+			// Topic
+			bp_activity_set_action(
+				buddypress()->groups->id,
+				$this->topic_create,
+				esc_html__( 'New forum topic', 'bbpress' ),
+				'bbp_format_activity_action_new_topic',
+				esc_html__( 'Topics', 'bbpress' ),
+				array( 'activity', 'member', 'member_groups', 'group' )
+			);
+
+			// Reply
+			bp_activity_set_action(
+				buddypress()->groups->id,
+				$this->reply_create,
+				esc_html__( 'New forum reply', 'bbpress' ),
+				'bbp_format_activity_action_new_reply',
+				esc_html__( 'Replies', 'bbpress' ),
+				array( 'activity', 'member', 'member_groups', 'group' )
+			);
+		}
+		*/
+	}
+
+	/**
+	 * Wrapper for recoding bbPress actions to the BuddyPress activity stream
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @param  array $args Array of arguments for bp_activity_add()
+	 *
+	 * @return int   Activity ID if successful, false if not
+	 */
+	private function record_activity( $args = array() ) {
+
+		// Default activity args
+		$activity = bbp_parse_args( $args, array(
+			'id'                => null,
+			'user_id'           => bbp_get_current_user_id(),
+			'type'              => '',
+			'action'            => '',
+			'item_id'           => '',
+			'secondary_item_id' => '',
+			'content'           => '',
+			'primary_link'      => '',
+			'component'         => $this->component,
+			'recorded_time'     => bp_core_current_time(),
+			'hide_sitewide'     => false
+		), 'record_activity' );
+
+		// Add the activity
+		return bp_activity_add( $activity );
+	}
+
+	/**
+	 * Wrapper for deleting bbPress actions from BuddyPress activity stream
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @param  array $args Array of arguments for bp_activity_add()
+	 *
+	 * @return int   Activity ID if successful, false if not
+	 */
+	public function delete_activity( $args = array() ) {
+
+		// Default activity args
+		$activity = bbp_parse_args( $args, array(
+			'item_id'           => false,
+			'component'         => $this->component,
+			'type'              => false,
+			'user_id'           => false,
+			'secondary_item_id' => false
+		), 'delete_activity' );
+
+		// Delete the activity
+		bp_activity_delete_by_item_id( $activity );
+	}
+
+	/**
+	 * Check for an existing activity stream entry for a given post_id
+	 *
+	 * @param int $post_id ID of the topic or reply
+	 * @return int if an activity id is verified, false if not
+	 */
+	private static function get_activity_id( $post_id = 0 ) {
+
+		// Try to get the activity ID of the post
+		$activity_id = (int) get_post_meta( $post_id, '_bbp_activity_id', true );
+
+		// Bail if no activity ID is in post meta
+		if ( empty( $activity_id ) ) {
+			return null;
+		}
+
+		// Get the activity stream item, bail if it doesn't exist
+		$existing = new BP_Activity_Activity( $activity_id );
+		if ( empty( $existing->component ) ) {
+			return null;
+		}
+
+		// Return the activity ID since we've verified the connection
+		return $activity_id;
+	}
+
+	/**
+	 * Maybe disable activity stream comments on select actions
+	 *
+	 * @since 2.0.0 bbPress (r3399)
+	 *
+	 * @global BP_Activity_Template $activities_template
+	 * @param boolean $can_comment
+	 * @return boolean
+	 */
+	public function activity_can_comment( $can_comment = true ) {
+		global $activities_template;
+
+		// Already forced off, so comply
+		if ( false === $can_comment ) {
+			return $can_comment;
+		}
+
+		// Check if blog & forum activity stream commenting is off
+		if ( ! empty( $activities_template->disable_blogforum_replies ) ) {
+
+			// Get the current action name
+			$action_name = bp_get_activity_action_name();
+
+			// Setup the array of possibly disabled actions
+			$disabled_actions = array(
+				$this->topic_create,
+				$this->reply_create
+			);
+
+			// Check if this activity stream action is disabled
+			if ( in_array( $action_name, $disabled_actions, true ) ) {
+				$can_comment = false;
+			}
+		}
+
+		return $can_comment;
+	}
+
+	/**
+	 * Maybe link directly to topics and replies in activity stream entries
+	 *
+	 * @since 2.0.0 bbPress (r3399)
+	 *
+	 * @param string $link
+	 * @param mixed $activity_object
+	 * @return string The link to the activity stream item
+	 */
+	public function activity_get_permalink( $link = '', $activity_object = false ) {
+
+		// Setup the array of actions to link directly to
+		$disabled_actions = array(
+			$this->topic_create,
+			$this->reply_create
+		);
+
+		// Check if this activity stream action is directly linked
+		if ( in_array( $activity_object->type, $disabled_actions, true ) ) {
+			$link = $activity_object->primary_link;
+		}
+
+		return $link;
+	}
+
+	/**
+	 * Append forum options to activity filter select box
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 */
+	function activity_filter_options() {
+	?>
+
+		<option value="<?php echo $this->topic_create; ?>"><?php esc_html_e( 'Topics',  'bbpress' ); ?></option>
+		<option value="<?php echo $this->reply_create; ?>"><?php esc_html_e( 'Replies', 'bbpress' ); ?></option>
+
+	<?php
+	}
+
+	/** Topics ****************************************************************/
+
+	/**
+	 * Record an activity stream entry when a topic is created or updated
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @param int $topic_id
+	 * @param int $forum_id
+	 * @param array $anonymous_data
+	 * @param int $topic_author_id
+	 * @return Bail early if topic is by anonymous user
+	 */
+	public function topic_create( $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $topic_author_id = 0 ) {
+
+		// Bail early if topic is by anonymous user
+		if ( ! empty( $anonymous_data ) ) {
+			return;
+		}
+
+		// Bail if site is private
+		if ( ! bbp_is_site_public() ) {
+			return;
+		}
+
+		// Validate activity data
+		$user_id  = bbp_get_user_id( $topic_author_id );
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Bail if user is not active
+		if ( bbp_is_user_inactive( $user_id ) ) {
+			return;
+		}
+
+		// Bail if topic is not published
+		if ( ! bbp_is_topic_published( $topic_id ) ) {
+			return;
+		}
+
+		// User link for topic author
+		$user_link = bbp_get_user_profile_link( $user_id  );
+
+		// Topic
+		$topic_permalink = bbp_get_topic_permalink( $topic_id );
+		$topic_title     = get_post_field( 'post_title',   $topic_id, 'raw' );
+		$topic_content   = get_post_field( 'post_content', $topic_id, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+
+		// Forum
+		$forum_permalink = bbp_get_forum_permalink( $forum_id );
+		$forum_title     = get_post_field( 'post_title', $forum_id, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+
+		// Activity action & text
+		$activity_text    = sprintf( esc_html__( '%1$s started the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+		$activity_action  = apply_filters( 'bbp_activity_topic_create',         $activity_text, $user_id,   $topic_id,   $forum_id );
+		$activity_content = apply_filters( 'bbp_activity_topic_create_excerpt', $topic_content                                     );
+
+		// Compile and record the activity stream results
+		$activity_id = $this->record_activity( array(
+			'id'                => $this->get_activity_id( $topic_id ),
+			'user_id'           => $user_id,
+			'action'            => $activity_action,
+			'content'           => $activity_content,
+			'primary_link'      => $topic_permalink,
+			'type'              => $this->topic_create,
+			'item_id'           => $topic_id,
+			'secondary_item_id' => $forum_id,
+			'recorded_time'     => get_post_time( 'Y-m-d H:i:s', true, $topic_id ),
+			'hide_sitewide'     => ! bbp_is_forum_public( $forum_id, false )
+		) );
+
+		// Add the activity entry ID as a meta value to the topic
+		if ( ! empty( $activity_id ) ) {
+			update_post_meta( $topic_id, '_bbp_activity_id', $activity_id );
+		}
+	}
+
+	/**
+	 * Delete the activity stream entry when a topic is spammed, trashed, or deleted
+	 *
+	 * @param int $topic_id
+	 */
+	public function topic_delete( $topic_id = 0 ) {
+
+		// Get activity ID, bail if it doesn't exist
+		$activity_id = $this->get_activity_id( $topic_id );
+		if ( ! empty( $activity_id ) ) {
+			return bp_activity_delete( array( 'id' => $activity_id ) );
+		}
+
+		return false;
+	}
+
+	/**
+	 * Update the activity stream entry when a topic status changes
+	 *
+	 * @param int $topic_id
+	 * @param obj $post
+	 * @return Bail early if not a topic, or topic is by anonymous user
+	 */
+	public function topic_update( $topic_id = 0, $post = null ) {
+
+		// Bail early if not a topic
+		if ( get_post_type( $post ) !== bbp_get_topic_post_type() ) {
+			return;
+		}
+
+		// Bail early if revisions are off
+		if ( ! bbp_allow_revisions() || ! post_type_supports( bbp_get_topic_post_type(), 'revisions' ) ) {
+			return;
+		}
+
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Bail early if topic is by anonymous user
+		if ( bbp_is_topic_anonymous( $topic_id ) ) {
+			return;
+		}
+
+		// Action based on new status
+		if ( bbp_is_topic_public( $post->ID ) ) {
+
+			// Validate topic data
+			$forum_id        = bbp_get_topic_forum_id( $topic_id );
+			$topic_author_id = bbp_get_topic_author_id( $topic_id );
+
+			$this->topic_create( $topic_id, $forum_id, array(), $topic_author_id );
+		} else {
+			$this->topic_delete( $topic_id );
+		}
+	}
+
+	/** Replies ***************************************************************/
+
+	/**
+	 * Record an activity stream entry when a reply is created
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @param int $topic_id
+	 * @param int $forum_id
+	 * @param array $anonymous_data
+	 * @param int $topic_author_id
+	 * @return Bail early if topic is by anonymous user
+	 */
+	public function reply_create( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $reply_author_id = 0 ) {
+
+		// Do not log activity of anonymous users
+		if ( ! empty( $anonymous_data ) ) {
+			return;
+		}
+
+		// Bail if site is private
+		if ( ! bbp_is_site_public() ) {
+			return;
+		}
+
+		// Validate activity data
+		$user_id  = bbp_get_user_id( $reply_author_id );
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Bail if user is not active
+		if ( bbp_is_user_inactive( $user_id ) ) {
+			return;
+		}
+
+		// Bail if reply is not published
+		if ( ! bbp_is_reply_published( $reply_id ) ) {
+			return;
+		}
+
+		// Setup links for activity stream
+		$user_link = bbp_get_user_profile_link( $user_id  );
+
+		// Reply
+		$reply_url     = bbp_get_reply_url( $reply_id );
+		$reply_content = get_post_field( 'post_content', $reply_id, 'raw' );
+
+		// Topic
+		$topic_permalink = bbp_get_topic_permalink( $topic_id );
+		$topic_title     = get_post_field( 'post_title', $topic_id, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+
+		// Forum
+		$forum_permalink = bbp_get_forum_permalink( $forum_id );
+		$forum_title     = get_post_field( 'post_title', $forum_id, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+
+		// Activity action & text
+		$activity_text    = sprintf( esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+		$activity_action  = apply_filters( 'bbp_activity_reply_create',         $activity_text, $user_id, $reply_id,  $topic_id );
+		$activity_content = apply_filters( 'bbp_activity_reply_create_excerpt', $reply_content                                  );
+
+		// Compile and record the activity stream results
+		$activity_id = $this->record_activity( array(
+			'id'                => $this->get_activity_id( $reply_id ),
+			'user_id'           => $user_id,
+			'action'            => $activity_action,
+			'content'           => $activity_content,
+			'primary_link'      => $reply_url,
+			'type'              => $this->reply_create,
+			'item_id'           => $reply_id,
+			'secondary_item_id' => $topic_id,
+			'recorded_time'     => get_post_time( 'Y-m-d H:i:s', true, $reply_id ),
+			'hide_sitewide'     => ! bbp_is_forum_public( $forum_id, false )
+		) );
+
+		// Add the activity entry ID as a meta value to the reply
+		if ( ! empty( $activity_id ) ) {
+			update_post_meta( $reply_id, '_bbp_activity_id', $activity_id );
+		}
+	}
+
+	/**
+	 * Delete the activity stream entry when a reply is spammed, trashed, or deleted
+	 *
+	 * @param int $reply_id
+	 */
+	public function reply_delete( $reply_id ) {
+
+		// Get activity ID, bail if it doesn't exist
+		$activity_id = $this->get_activity_id( $reply_id );
+		if ( ! empty( $activity_id ) ) {
+			return bp_activity_delete( array( 'id' => $activity_id ) );
+		}
+
+		return false;
+	}
+
+	/**
+	 * Update the activity stream entry when a reply status changes
+	 *
+	 * @param int $reply_id
+	 * @param obj $post
+	 * @return Bail early if not a reply, or reply is by anonymous user
+	 */
+	public function reply_update( $reply_id, $post ) {
+
+		// Bail early if not a reply
+		if ( get_post_type( $post ) !== bbp_get_reply_post_type() ) {
+			return;
+		}
+
+		// Bail early if revisions are off
+		if ( ! bbp_allow_revisions() || ! post_type_supports( bbp_get_reply_post_type(), 'revisions' ) ) {
+			return;
+		}
+
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Bail early if reply is by anonymous user
+		if ( bbp_is_reply_anonymous( $reply_id ) ) {
+			return;
+		}
+
+		// Action based on new status
+		if ( bbp_get_public_status_id() === $post->post_status ) {
+
+			// Validate reply data
+			$topic_id        = bbp_get_reply_topic_id( $reply_id );
+			$forum_id        = bbp_get_reply_forum_id( $reply_id );
+			$reply_author_id = bbp_get_reply_author_id( $reply_id );
+
+			$this->reply_create( $reply_id, $topic_id, $forum_id, array(), $reply_author_id );
+		} else {
+			$this->reply_delete( $reply_id );
+		}
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/extend/buddypress/functions.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/functions.php	(revision 7162)
@@ -0,0 +1,908 @@
+<?php
+
+/**
+ * Main bbPress BuddyPress Class
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Hooks
+add_filter( 'bp_modify_page_title', 'bbp_filter_modify_page_title', 10, 3 );
+add_filter( 'bbp_get_user_id',      'bbp_filter_user_id',           10, 3 );
+add_filter( 'bbp_is_single_user',   'bbp_filter_is_single_user',    10, 1 );
+add_filter( 'bbp_is_user_home',     'bbp_filter_is_user_home',      10, 1 );
+
+// Group Forum Root
+add_action( 'load-settings_page_bbpress', 'bbp_maybe_create_group_forum_root' );
+add_action( 'bbp_delete_forum',           'bbp_maybe_delete_group_forum_root' );
+
+/** BuddyPress Helpers ********************************************************/
+
+/**
+ * Return component name/ID ('forums' by default)
+ *
+ * This is used primarily for Notifications integration.
+ *
+ * @since 2.6.0 bbPress (r5232)
+ *
+ * @return string
+ */
+function bbp_get_component_name() {
+
+	// Use existing ID or default
+	$retval = ! empty( bbpress()->extend->buddypress->id )
+		? bbpress()->extend->buddypress->id
+		: 'forums';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_component_name', $retval );
+}
+
+/**
+ * Filter the current bbPress user ID with the current BuddyPress user ID
+ *
+ * @since 2.1.0 bbPress (r3552)
+ *
+ * @param int $user_id
+ * @param bool $displayed_user_fallback
+ * @param bool $current_user_fallback
+ *
+ * @return int User ID
+ */
+function bbp_filter_user_id( $user_id = 0, $displayed_user_fallback = true, $current_user_fallback = false ) {
+
+	// Define local variable
+	$bbp_user_id = 0;
+
+	// Get possible user ID's
+	$did = bp_displayed_user_id();
+	$lid = bp_loggedin_user_id();
+
+	// Easy empty checking
+	if ( ! empty( $user_id ) && is_numeric( $user_id ) ) {
+		$bbp_user_id = $user_id;
+
+	// Currently viewing or editing a user
+	} elseif ( ( true === $displayed_user_fallback ) && ! empty( $did ) ) {
+		$bbp_user_id = $did;
+
+	// Maybe fallback on the current_user ID
+	} elseif ( ( true === $current_user_fallback ) && ! empty( $lid ) ) {
+		$bbp_user_id = $lid;
+	}
+
+	return $bbp_user_id;
+}
+
+/**
+ * Filter the bbPress is_single_user function with BuddyPress equivalent
+ *
+ * @since 2.1.0 bbPress (r3552)
+ *
+ * @param bool $is Optional. Default false
+ * @return bool True if viewing single user, false if not
+ */
+function bbp_filter_is_single_user( $is = false ) {
+	if ( ! empty( $is ) ) {
+		return $is;
+	}
+
+	return bp_is_user();
+}
+
+/**
+ * Filter the bbPress is_user_home function with BuddyPress equivalent
+ *
+ * @since 2.1.0 bbPress (r3552)
+ *
+ * @param bool $is Optional. Default false
+ * @return bool True if viewing single user, false if not
+ */
+function bbp_filter_is_user_home( $is = false ) {
+	if ( ! empty( $is ) ) {
+		return $is;
+	}
+
+	return bp_is_my_profile();
+}
+
+/**
+ * Add the topic title to the <title> if viewing a single group forum topic
+ *
+ * @since 2.5.0 bbPress (r5161)
+ *
+ * @param string $new_title The title to filter
+ * @param string $old_title (Not used)
+ * @param string $sep The separator to use
+ * @return string The possibly modified title
+ */
+function bbp_filter_modify_page_title( $new_title = '', $old_title = '', $sep = '' ) {
+
+	// Only filter if group forums are active
+	if ( bbp_is_group_forums_active() ) {
+
+		// Only filter for single group forum topics
+		if ( bp_is_group_forum_topic() || bp_is_group_forum_topic_edit() ) {
+
+			// Get the topic
+			$topic = get_posts( array(
+				'name'        => bp_action_variable( 1 ),
+				'post_status' => array_keys( bbp_get_topic_statuses() ),
+				'post_type'   => bbp_get_topic_post_type(),
+				'numberposts' => 1
+			) );
+
+			// Add the topic title to the <title>
+			$new_title .= bbp_get_topic_title( $topic[0]->ID ) . ' ' . $sep . ' ';
+		}
+	}
+
+	// Return the title
+	return $new_title;
+}
+
+/** BuddyPress Screens ********************************************************/
+
+/**
+ * Hook bbPress topics template into plugins template
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_screen_topics() {
+	add_action( 'bp_template_content', 'bbp_member_forums_topics_content' );
+	bp_core_load_template( apply_filters( 'bbp_member_forums_screen_topics', 'members/single/plugins' ) );
+}
+
+/**
+ * Hook bbPress replies template into plugins template
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_screen_replies() {
+	add_action( 'bp_template_content', 'bbp_member_forums_replies_content' );
+	bp_core_load_template( apply_filters( 'bbp_member_forums_screen_replies', 'members/single/plugins' ) );
+}
+
+/**
+ * Hook bbPress engagements template into plugins template
+ *
+ * @since 2.6.0 bbPress (r6320)
+ */
+function bbp_member_forums_screen_engagements() {
+	add_action( 'bp_template_content', 'bbp_member_forums_engagements_content' );
+	bp_core_load_template( apply_filters( 'bbp_member_forums_screen_engagements', 'members/single/plugins' ) );
+}
+
+/**
+ * Hook bbPress favorites template into plugins template
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_screen_favorites() {
+	add_action( 'bp_template_content', 'bbp_member_forums_favorites_content' );
+	bp_core_load_template( apply_filters( 'bbp_member_forums_screen_favorites', 'members/single/plugins' ) );
+}
+
+/**
+ * Hook bbPress subscriptions template into plugins template
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_screen_subscriptions() {
+	add_action( 'bp_template_content', 'bbp_member_forums_subscriptions_content' );
+	bp_core_load_template( apply_filters( 'bbp_member_forums_screen_subscriptions', 'members/single/plugins' ) );
+}
+
+/** BuddyPress Templates ******************************************************/
+
+/**
+ * Get the topics created template part
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_topics_content() {
+?>
+
+	<div id="bbpress-forums" class="bbpress-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'topics-created' ); ?>
+
+	</div>
+
+<?php
+}
+
+/**
+ * Get the topics replied to template part
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_replies_content() {
+?>
+
+	<div id="bbpress-forums" class="bbpress-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'replies-created' ); ?>
+
+	</div>
+
+<?php
+}
+
+/**
+ * Get the topic engagements template part
+ *
+ * @since 2.6.0 bbPress (r6320)
+ */
+function bbp_member_forums_engagements_content() {
+?>
+
+	<div id="bbpress-forums" class="bbpress-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'engagements' ); ?>
+
+	</div>
+
+<?php
+}
+
+/**
+ * Get the topics favorited template part
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_favorites_content() {
+?>
+
+	<div id="bbpress-forums" class="bbpress-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'favorites' ); ?>
+
+	</div>
+
+<?php
+}
+
+/**
+ * Get the topics subscribed template part
+ *
+ * @since 2.1.0 bbPress (r3552)
+ */
+function bbp_member_forums_subscriptions_content() {
+?>
+
+	<div id="bbpress-forums" class="bbpress-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'subscriptions' ); ?>
+
+	</div>
+
+<?php
+}
+
+/** Forum Group Root **********************************************************/
+
+/**
+ * Clean up the group root setting if the forum is being deleted
+ *
+ * @since 2.6.0 bbPress (r6479)
+ *
+ * @param int $forum_id The forum ID being deleted
+ */
+function bbp_maybe_delete_group_forum_root( $forum_id = 0 ) {
+
+	// Bail if no forum ID
+	$forum_id = bbp_get_forum_id();
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// Get the group root
+	$group_root = (int) get_option( '_bbp_group_forums_root_id', 0 );
+
+	// Delete the group root if the forum just got deleted
+	if ( $group_root === $forum_id ) {
+		delete_option( '_bbp_group_forums_root_id' );
+	}
+}
+
+/**
+ * Handle the new group forum root creation
+ *
+ * @since 2.6.0 bbPress (r6479)
+ *
+ * @return
+ */
+function bbp_maybe_create_group_forum_root() {
+
+	// Bail if no nonce
+	if ( empty( $_GET['_wpnonce'] ) || ( empty( $_GET['create'] ) || ( 'bbp-group-forum-root' !== $_GET['create'] ) ) ) {
+		return;
+	}
+
+	// Bail if user cannot publish forums
+	if ( ! current_user_can( 'publish_forums' ) ) {
+		return;
+	}
+
+	// Bail if nonce check fails
+	if ( ! wp_verify_nonce( $_GET['_wpnonce'], '_bbp_group_forums_root_id' ) ) {
+		return;
+	}
+
+	// Create new forum
+	$forum_id = bbp_insert_forum(
+
+		// Post
+		array( 'post_title' => esc_html__( 'Group Forums', 'bbpress' ) ),
+
+		// Meta
+		array( 'forum_type' => 'category' )
+	);
+
+	// Update & redirect
+	if ( ! empty( $forum_id ) ) {
+
+		// Create
+		update_option( '_bbp_group_forums_root_id', $forum_id );
+
+		// Redirect
+		bbp_redirect( add_query_arg( array(
+			'page'    => 'bbpress',
+			'updated' => true
+		), admin_url( 'options-general.php' ) ) );
+	}
+}
+
+/** Forum/Group Sync **********************************************************/
+
+/**
+ * These functions are used to keep the many-to-many relationships between
+ * groups and forums synchronized. Each forum and group stores pointers to each
+ * other in their respective meta. This way if a group or forum is deleted
+ * their associations can be updated without much effort.
+ */
+
+/**
+ * Get forum ID's for a group
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_get_group_forum_ids( $group_id = 0 ) {
+
+	// Assume no forums
+	$forum_ids = array();
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get the forums
+	if ( ! empty( $group_id ) ) {
+		$forum_ids = groups_get_groupmeta( $group_id, 'forum_id' );
+	}
+
+	// Make sure result is an array of ints
+	$forum_ids = array_filter( wp_parse_id_list( $forum_ids ) );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_group_forum_ids', $forum_ids, $group_id );
+}
+
+/**
+ * Get group ID's for a forum
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $forum_id
+ */
+function bbp_get_forum_group_ids( $forum_id = 0 ) {
+
+	// Assume no forums
+	$group_ids = array();
+
+	// Use current group if none is set
+	if ( empty( $forum_id ) ) {
+		$forum_id = bbp_get_forum_id();
+	}
+
+	// Get the forums
+	if ( ! empty( $forum_id ) ) {
+		$group_ids = get_post_meta( $forum_id, '_bbp_group_ids', true );
+	}
+
+	// Make sure result is an array of ints
+	$group_ids = array_filter( wp_parse_id_list( $group_ids ) );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_group_ids', $group_ids, $forum_id );
+}
+
+/**
+ * Get forum ID's for a group
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_update_group_forum_ids( $group_id = 0, $forum_ids = array() ) {
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Trim out any empties
+	$forum_ids = array_filter( wp_parse_id_list( $forum_ids ) );
+
+	// Get the forums
+	return groups_update_groupmeta( $group_id, 'forum_id', $forum_ids );
+}
+
+/**
+ * Update group ID's for a forum
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $forum_id
+ */
+function bbp_update_forum_group_ids( $forum_id = 0, $group_ids = array() ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Trim out any empties
+	$group_ids = array_filter( wp_parse_id_list( $group_ids ) );
+
+	// Get the forums
+	return update_post_meta( $forum_id, '_bbp_group_ids', $group_ids );
+}
+
+/**
+ * Add a group to a forum
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_add_group_id_to_forum( $forum_id = 0, $group_id = 0 ) {
+
+	// Validate forum_id
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get current group IDs
+	$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+	// Maybe update the groups forums
+	if ( ! in_array( $group_id, $group_ids, true ) ) {
+		$group_ids[] = $group_id;
+		return bbp_update_forum_group_ids( $forum_id, $group_ids );
+	}
+}
+
+/**
+ * Remove a forum from a group
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_add_forum_id_to_group( $group_id = 0, $forum_id = 0 ) {
+
+	// Validate forum_id
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get current group IDs
+	$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+	// Maybe update the groups forums
+	if ( ! in_array( $forum_id, $forum_ids, true ) ) {
+		$forum_ids[] = $forum_id;
+		return bbp_update_group_forum_ids( $group_id, $forum_ids );
+	}
+}
+
+/**
+ * Remove a group from a forum
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_remove_group_id_from_forum( $forum_id = 0, $group_id = 0 ) {
+
+	// Validate forum_id
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get current group IDs
+	$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+	// Maybe update the groups forums
+	if ( in_array( $group_id, $group_ids, true ) ) {
+		$group_ids = array_diff( array_values( $group_ids ), (array) $group_id );
+		return bbp_update_forum_group_ids( $forum_id, $group_ids );
+	}
+}
+
+/**
+ * Remove a forum from a group
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_remove_forum_id_from_group( $group_id = 0, $forum_id = 0 ) {
+
+	// Validate forum_id
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get current group IDs
+	$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+	// Maybe update the groups forums
+	if ( in_array( $forum_id, $forum_ids, true ) ) {
+		$forum_ids = array_diff( array_values( $forum_ids ), (array) $forum_id );
+		return bbp_update_group_forum_ids( $group_id, $forum_ids );
+	}
+}
+
+/**
+ * Remove a group from all forums
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $group_id
+ */
+function bbp_remove_group_id_from_all_forums( $group_id = 0 ) {
+
+	// Use current group if none is set
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	// Get current group IDs
+	$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+	// Loop through forums and remove this group from each one
+	foreach ( $forum_ids as $forum_id ) {
+		bbp_remove_group_id_from_forum( $group_id, $forum_id );
+	}
+}
+
+/**
+ * Remove a forum from all groups
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @param int $forum_id
+ */
+function bbp_remove_forum_id_from_all_groups( $forum_id = 0 ) {
+
+	// Validate
+	$forum_id  = bbp_get_forum_id( $forum_id );
+	$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+	// Loop through groups and remove this forum from each one
+	foreach ( $group_ids as $group_id ) {
+		bbp_remove_forum_id_from_group( $forum_id, $group_id );
+	}
+}
+
+/**
+ * Return true if a forum is a group forum
+ *
+ * @since 2.3.0 bbPress (r4571)
+ *
+ * @param int $forum_id
+ * @return bool True if it is a group forum, false if not
+ */
+function bbp_is_forum_group_forum( $forum_id = 0 ) {
+
+	// Validate
+	$forum_id  = bbp_get_forum_id( $forum_id );
+
+	// Check for group ID's
+	$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+	// Check if the forum has groups
+	$retval    = (bool) ! empty( $group_ids );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_group_forum', $retval, $forum_id, $group_ids );
+}
+
+/*** Group Member Status ******************************************************/
+
+/**
+ * Is the current user an admin of the current group
+ *
+ * @since 2.3.0 bbPress (r4632)
+ *
+ * @return bool If current user is an admin of the current group
+ */
+function bbp_group_is_admin() {
+
+	// Bail if user is not logged in or not looking at a group
+	if ( ! is_user_logged_in() || ! bp_is_group() ) {
+		return false;
+	}
+
+	$bbp = bbpress();
+
+	// Set the global if not set
+	if ( ! isset( $bbp->current_user->is_group_admin ) ) {
+		$bbp->current_user->is_group_admin = groups_is_user_admin( bp_loggedin_user_id(), bp_get_current_group_id() );
+	}
+
+	// Return the value
+	return (bool) $bbp->current_user->is_group_admin;
+}
+
+/**
+ * Is the current user a moderator of the current group
+ *
+ * @since 2.3.0 bbPress (r4632)
+ *
+ * @return bool If current user is a moderator of the current group
+ */
+function bbp_group_is_mod() {
+
+	// Bail if user is not logged in or not looking at a group
+	if ( ! is_user_logged_in() || ! bp_is_group() ) {
+		return false;
+	}
+
+	$bbp = bbpress();
+
+	// Set the global if not set
+	if ( ! isset( $bbp->current_user->is_group_mod ) ) {
+		$bbp->current_user->is_group_mod = groups_is_user_mod( bp_loggedin_user_id(), bp_get_current_group_id() );
+	}
+
+	// Return the value
+	return (bool) $bbp->current_user->is_group_mod;
+}
+
+/**
+ * Is the current user a member of the current group
+ *
+ * @since 2.3.0 bbPress (r4632)
+ *
+ * @return bool If current user is a member of the current group
+ */
+function bbp_group_is_member() {
+
+	// Bail if user is not logged in or not looking at a group
+	if ( ! is_user_logged_in() || ! bp_is_group() ) {
+		return false;
+	}
+
+	$bbp = bbpress();
+
+	// Set the global if not set
+	if ( ! isset( $bbp->current_user->is_group_member ) ) {
+		$bbp->current_user->is_group_member = groups_is_user_member( bp_loggedin_user_id(), bp_get_current_group_id() );
+	}
+
+	// Return the value
+	return (bool) $bbp->current_user->is_group_member;
+}
+
+/**
+ * Is the current user banned from the current group
+ *
+ * @since 2.3.0 bbPress (r4632)
+ *
+ * @return bool If current user is banned from the current group
+ */
+function bbp_group_is_banned() {
+
+	// Bail if user is not logged in or not looking at a group
+	if ( ! is_user_logged_in() || ! bp_is_group() ) {
+		return false;
+	}
+
+	$bbp = bbpress();
+
+	// Set the global if not set
+	if ( ! isset( $bbp->current_user->is_group_banned ) ) {
+		$bbp->current_user->is_group_banned = groups_is_user_banned( bp_loggedin_user_id(), bp_get_current_group_id() );
+	}
+
+	// Return the value
+	return (bool) $bbp->current_user->is_group_banned;
+}
+
+/**
+ * Is the current user the creator of the current group
+ *
+ * @since 2.3.0 bbPress (r4632)
+ *
+ * @return bool If current user the creator of the current group
+ */
+function bbp_group_is_creator() {
+
+	// Bail if user is not logged in or not looking at a group
+	if ( ! is_user_logged_in() || ! bp_is_group() ) {
+		return false;
+	}
+
+	$bbp = bbpress();
+
+	// Set the global if not set
+	if ( ! isset( $bbp->current_user->is_group_creator ) ) {
+		$bbp->current_user->is_group_creator = groups_is_user_creator( bp_loggedin_user_id(), bp_get_current_group_id() );
+	}
+
+	// Return the value
+	return (bool) $bbp->current_user->is_group_creator;
+}
+
+/* BuddyPress Activity Action Callbacks ***************************************/
+
+/**
+ * Return an array of allowed activity actions
+ *
+ * @since 2.6.0 bbPress (r6370)
+ *
+ * @return array
+ */
+function bbp_get_activity_actions() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_activity_actions', array(
+		'topic' => esc_html__( '%1$s started the topic %2$s in the forum %3$s',    'bbpress' ),
+		'reply' => esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' )
+	) );
+}
+
+/**
+ * Generic function to format the dynamic BuddyPress activity action for new
+ * topics/replies.
+ *
+ * @since 2.6.0 bbPress (r6370)
+ *
+ * @param string               $type     The type of post. Expects `topic` or `reply`.
+ * @param string               $action   The current action string.
+ * @param BP_Activity_Activity $activity The BuddyPress activity object.
+ *
+ * @return string The formatted activity action.
+ */
+function bbp_format_activity_action_new_post( $type = '', $action = '', $activity = false ) {
+
+	// Get actions
+	$actions = bbp_get_activity_actions();
+
+	// Bail early if we don't have a valid type
+	if ( ! in_array( $type, array_keys( $actions ), true ) ) {
+		return $action;
+	}
+
+	// Bail if intercepted
+	$intercept = bbp_maybe_intercept( __FUNCTION__, func_get_args() );
+	if ( bbp_is_intercepted( $intercept ) ) {
+		return $intercept;
+	}
+
+	// Groups component
+	if ( 'groups' === $activity->component ) {
+		if ( 'topic' === $type ) {
+			$topic_id = bbp_get_topic_id( $activity->secondary_item_id );
+			$forum_id = bbp_get_topic_forum_id( $topic_id );
+		} else {
+			$topic_id = bbp_get_reply_topic_id( $activity->secondary_item_id );
+			$forum_id = bbp_get_topic_forum_id( $topic_id );
+		}
+
+	// General component (bbpress/forums/other)
+	} else {
+		if ( 'topic' === $type ) {
+			$topic_id = bbp_get_topic_id( $activity->item_id );
+			$forum_id = bbp_get_forum_id( $activity->secondary_item_id );
+		} else {
+			$topic_id = bbp_get_topic_id( $activity->secondary_item_id );
+			$forum_id = bbp_get_topic_forum_id( $topic_id );
+		}
+	}
+
+	// User link for topic author
+	$user_link = bbp_get_user_profile_link( $activity->user_id );
+
+	// Topic link
+	$topic_permalink = bbp_get_topic_permalink( $topic_id );
+	$topic_title     = get_post_field( 'post_title', $topic_id, 'raw' );
+	$topic_link      = '<a href="' . esc_url( $topic_permalink ) . '">' . esc_html( $topic_title ) . '</a>';
+
+	// Forum link
+	$forum_permalink = bbp_get_forum_permalink( $forum_id );
+	$forum_title     = get_post_field( 'post_title', $forum_id, 'raw' );
+	$forum_link      = '<a href="' . esc_url( $forum_permalink ) . '">' . esc_html( $forum_title ) . '</a>';
+
+	// Format
+	$activity_action = sprintf( $actions[ $type ], $user_link, $topic_link, $forum_link );
+
+	/**
+	 * Filters the formatted activity action new activity string.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @param string               $activity_action Activity action string value
+	 * @param string               $type            The type of post. Expects `topic` or `reply`.
+	 * @param string               $action          The current action string.
+	 * @param BP_Activity_Activity $activity        The BuddyPress activity object.
+	 */
+	return apply_filters( 'bbp_format_activity_action_new_post', $activity_action, $type, $action, $activity );
+}
+
+/**
+ * Formats the dynamic BuddyPress activity action for new topics.
+ *
+ * @since 2.6.0 bbPress (r6370)
+ *
+ * @param string $action   The current action string
+ * @param object $activity The BuddyPress activity object
+ *
+ * @return string The formatted activity action.
+ */
+function bbp_format_activity_action_new_topic( $action, $activity ) {
+	$action = bbp_format_activity_action_new_post( bbp_get_topic_post_type(), $action, $activity );
+
+	/**
+	 * Filters the formatted activity action new topic string.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @param string               $action   Activity action string value
+	 * @param BP_Activity_Activity $activity Activity item object
+	 */
+	return apply_filters( 'bbp_format_activity_action_new_topic', $action, $activity );
+}
+
+/**
+ * Formats the dynamic BuddyPress activity action for new replies.
+ *
+ * @since 2.6.0 bbPress (r6370)
+ *
+ * @param string $action   The current action string
+ * @param object $activity The BuddyPress activity object
+ *
+ * @return string The formatted activity action
+ */
+function bbp_format_activity_action_new_reply( $action, $activity ) {
+	$action = bbp_format_activity_action_new_post( bbp_get_reply_post_type(), $action, $activity );
+
+	/**
+	 * Filters the formatted activity action new reply string.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @param string               $action   Activity action string value
+	 * @param BP_Activity_Activity $activity Activity item object
+	 */
+	return apply_filters( 'bbp_format_activity_action_new_reply', $action, $activity );
+}
Index: /tags/2.6.6/src/includes/extend/buddypress/groups.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/groups.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/groups.php	(revision 7162)
@@ -0,0 +1,1695 @@
+<?php
+
+/**
+ * bbPress BuddyPress Group Extension Class
+ *
+ * This file is responsible for connecting bbPress to the BuddyPress Groups
+ * Component. It's a great example of how to perform both simple and advanced
+ * techniques to manipulate the default output provided by both.
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Forums_Group_Extension' ) && class_exists( 'BP_Group_Extension' ) ) :
+/**
+ * Loads Group Extension for Forums Component
+ *
+ * @since 2.1.0 bbPress (r3552)
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+class BBP_Forums_Group_Extension extends BP_Group_Extension {
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Setup bbPress group extension variables
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function __construct() {
+		$this->setup_variables();
+		$this->setup_actions();
+		$this->setup_filters();
+		$this->maybe_unset_forum_menu();
+		$this->fully_loaded();
+	}
+
+	/**
+	 * Setup the group forums class variables
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	private function setup_variables() {
+
+		// Component Name
+		$this->name          = esc_html__( 'Forum', 'bbpress' );
+		$this->nav_item_name = esc_html__( 'Forum', 'bbpress' );
+
+		// Component slugs (hardcoded to match bbPress 1.x functionality)
+		$this->slug          = 'forum';
+		$this->topic_slug    = 'topic';
+		$this->reply_slug    = 'reply';
+
+		// Forum component is visible
+		$this->visibility = 'public';
+
+		// Set positions towards end
+		$this->create_step_position = 15;
+		$this->nav_item_position    = 10;
+
+		// Allow create step and show in nav
+		$this->enable_create_step   = true;
+		$this->enable_nav_item      = true;
+		$this->enable_edit_item     = true;
+
+		// Template file to load, and action to hook display on to
+		$this->template_file        = 'groups/single/plugins';
+		$this->display_hook         = 'bp_template_content';
+	}
+
+	/**
+	 * Setup the group forums class actions
+	 *
+	 * @since 2.3.0 bbPress (r4552)
+	 */
+	private function setup_actions() {
+
+		// Possibly redirect
+		add_action( 'bbp_template_redirect',          array( $this, 'redirect_canonical'              ) );
+
+		// Remove group forum cap map when view is done
+		add_action( 'bbp_after_group_forum_display',  array( $this, 'remove_group_forum_meta_cap_map' ) );
+
+		// Validate group IDs when editing topics & replies
+		add_action( 'bbp_edit_topic_pre_extras',      array( $this, 'validate_topic_forum_id' ) );
+		add_action( 'bbp_edit_reply_pre_extras',      array( $this, 'validate_reply_to_id'    ) );
+
+		// Check if group-forum status should be changed
+		add_action( 'groups_group_after_save',        array( $this, 'update_group_forum_visibility'   ) );
+
+		// bbPress needs to listen to BuddyPress group deletion
+		add_action( 'groups_before_delete_group',     array( $this, 'disconnect_forum_from_group'     ) );
+
+		// Adds a bbPress meta-box to the new BuddyPress Group Admin UI
+		add_action( 'bp_groups_admin_meta_boxes',     array( $this, 'group_admin_ui_edit_screen'      ) );
+
+		// Saves the bbPress options if they come from the BuddyPress Group Admin UI
+		add_action( 'bp_group_admin_edit_after',      array( $this, 'edit_screen_save'                ) );
+
+		// Adds a hidden input value to the "Group Settings" page
+		add_action( 'bp_before_group_settings_admin', array( $this, 'group_settings_hidden_field'     ) );
+	}
+
+	/**
+	 * Setup the group forums class filters
+	 *
+	 * @since 2.3.0 bbPress (r4552)
+	 */
+	private function setup_filters() {
+
+		// Group forum pagination
+		add_filter( 'bbp_topic_pagination',      array( $this, 'topic_pagination'   ) );
+		add_filter( 'bbp_replies_pagination',    array( $this, 'replies_pagination' ) );
+
+		// Tweak the redirect field
+		add_filter( 'bbp_new_topic_redirect_to', array( $this, 'new_topic_redirect_to'        ), 10, 3 );
+		add_filter( 'bbp_new_reply_redirect_to', array( $this, 'new_reply_redirect_to'        ), 10, 3 );
+
+		// Map forum/topic/reply permalinks to their groups
+		add_filter( 'bbp_get_forum_permalink',   array( $this, 'map_forum_permalink_to_group' ), 10, 2 );
+		add_filter( 'bbp_get_topic_permalink',   array( $this, 'map_topic_permalink_to_group' ), 10, 2 );
+		add_filter( 'bbp_get_reply_permalink',   array( $this, 'map_reply_permalink_to_group' ), 10, 2 );
+
+		// Map reply edit links to their groups
+		add_filter( 'bbp_get_reply_edit_url',    array( $this, 'map_reply_edit_url_to_group'  ), 10, 2 );
+
+		// Map assorted template function permalinks
+		add_filter( 'post_link',                 array( $this, 'post_link'                    ), 10, 2 );
+		add_filter( 'page_link',                 array( $this, 'page_link'                    ), 10, 2 );
+		add_filter( 'post_type_link',            array( $this, 'post_type_link'               ), 10, 2 );
+
+		// Map group forum activity items to groups
+		add_filter( 'bbp_before_record_activity_parse_args', array( $this, 'map_activity_to_group' ) );
+
+		/** Caps **************************************************************/
+
+		// Only add these filters if inside a group forum
+		if ( bp_is_single_item() && bp_is_group() && bp_is_current_action( $this->slug ) ) {
+
+			// Ensure bbp_is_single_forum() returns true on group forums.
+			add_filter( 'bbp_is_single_forum', array( $this, 'is_single_forum' ) );
+
+			// Ensure bbp_is_single_topic() returns true on group forum topics.
+			add_filter( 'bbp_is_single_topic', array( $this, 'is_single_topic' ) );
+
+			// Allow group member to view private/hidden forums
+			add_filter( 'bbp_map_meta_caps', array( $this, 'map_group_forum_meta_caps' ), 10, 4 );
+
+			// Group member permissions to view the topic and reply forms
+			add_filter( 'bbp_current_user_can_access_create_topic_form', array( $this, 'form_permissions' ) );
+			add_filter( 'bbp_current_user_can_access_create_reply_form', array( $this, 'form_permissions' ) );
+		}
+	}
+
+	/**
+	 * Allow the variables, actions, and filters to be modified by third party
+	 * plugins and themes.
+	 *
+	 * @since 2.6.0 bbPress (r6808)
+	 */
+	private function fully_loaded() {
+		do_action_ref_array( 'bbp_buddypress_groups_loaded', array( $this ) );
+	}
+
+	/**
+	 * Ensure that bbp_is_single_forum() returns true on group forum pages.
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/2974
+	 *
+	 * @since 2.6.0 bbPress (r6366)
+	 *
+	 * @param  bool $retval Current boolean.
+	 * @return bool
+	 */
+	public function is_single_forum( $retval = false ) {
+
+		// Additional BuddyPress specific single-forum conditionals
+		if ( ( false === $retval ) && ! bp_is_action_variable( $this->topic_slug, 0 ) ) {
+			$retval = true;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Ensure that bbp_is_single_topic() returns true on group forum topic pages.
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/2974
+	 *
+	 * @since 2.6.0 bbPress (r6220)
+	 *
+	 * @param  bool $retval Current boolean.
+	 * @return bool
+	 */
+	public function is_single_topic( $retval = false ) {
+
+		// Additional BuddyPress specific single-topic conditionals
+		if ( ( false === $retval ) && bp_is_action_variable( $this->topic_slug, 0 ) && bp_action_variable( 1 ) ) {
+			$retval = true;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * The primary display function for group forums
+	 *
+	 * @since 2.1.0 bbPress (r3746)
+	 *
+	 * @param int $group_id ID of the current group. Available only on BP 2.2+.
+	 */
+	public function display( $group_id = null ) {
+
+		// Prevent Topic Parent from appearing
+		add_action( 'bbp_theme_before_topic_form_forum', array( $this, 'ob_start'     ) );
+		add_action( 'bbp_theme_after_topic_form_forum',  array( $this, 'ob_end_clean' ) );
+		add_action( 'bbp_theme_after_topic_form_forum',  array( $this, 'topic_parent' ) );
+
+		// Prevent Forum Parent from appearing
+		add_action( 'bbp_theme_before_forum_form_parent', array( $this, 'ob_start'     ) );
+		add_action( 'bbp_theme_after_forum_form_parent',  array( $this, 'ob_end_clean' ) );
+		add_action( 'bbp_theme_after_forum_form_parent',  array( $this, 'forum_parent' ) );
+
+		// Hide breadcrumb
+		add_filter( 'bbp_no_breadcrumb', '__return_true' );
+
+		$this->display_forums( 0 );
+	}
+
+	/**
+	 * Maybe unset the group forum nav item if group does not have a forum
+	 *
+	 * @since 2.3.0 bbPress (r4552)
+	 *
+	 * @return If not viewing a single group
+	 */
+	public function maybe_unset_forum_menu() {
+
+		// Bail if not viewing a single group
+		if ( ! bp_is_group() ) {
+			return;
+		}
+
+		// Are forums enabled for this group?
+		$checked = bp_get_new_group_enable_forum() || groups_get_groupmeta( bp_get_new_group_id(), 'forum_id' );
+
+		// Tweak the nav item variable based on if group has forum or not
+		$this->enable_nav_item = (bool) $checked;
+	}
+
+	/**
+	 * Allow group members to have advanced privileges in group forum topics.
+	 *
+	 * @since 2.2.0 bbPress (r4434)
+	 *
+	 * @param array $caps
+	 * @param string $cap
+	 * @param int $user_id
+	 * @param array $args
+	 * @return array
+	 */
+	public function map_group_forum_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+		switch ( $cap ) {
+
+			// If user is a group mmember, allow them to create content.
+			case 'read_forum'          :
+			case 'publish_replies'     :
+			case 'publish_topics'      :
+			case 'read_hidden_forums'  :
+			case 'read_private_forums' :
+				if ( bbp_group_is_banned() ) {
+					$caps = array( 'do_not_allow' );
+				} elseif ( bbp_group_is_member() || bbp_group_is_mod() || bbp_group_is_admin() ) {
+					$caps = array( 'participate' );
+				}
+				break;
+
+			// If user is a group mod ar admin, map to participate cap.
+			case 'moderate'            :
+			case 'edit_topic'          :
+			case 'edit_reply'          :
+			case 'view_trash'          :
+			case 'edit_others_replies' :
+			case 'edit_others_topics'  :
+				if ( bbp_group_is_mod() || bbp_group_is_admin() ) {
+					$caps = array( 'participate' );
+				}
+				break;
+
+			// If user is a group admin, allow them to delete topics and replies.
+			case 'delete_topic' :
+			case 'delete_reply' :
+				if ( bbp_group_is_admin() ) {
+					$caps = array( 'participate' );
+				}
+				break;
+		}
+
+		// Filter & return
+		return (array) apply_filters( 'bbp_map_group_forum_topic_meta_caps', $caps, $cap, $user_id, $args );
+	}
+
+	/**
+	 * Remove the topic meta cap map, so it doesn't interfere with sidebars.
+	 *
+	 * @since 2.2.0 bbPress (r4434)
+	 */
+	public function remove_group_forum_meta_cap_map() {
+		remove_filter( 'bbp_map_meta_caps', array( $this, 'map_group_forum_meta_caps' ), 99, 4 );
+	}
+
+	/**
+	 * Validate the forum ID for a topic in a group forum.
+	 *
+	 * This method ensures that when a topic is saved, it is only allowed to be
+	 * saved to a forum that is either:
+	 *
+	 * - A forum in the current group
+	 * - A forum the current user can moderate outside of this group
+	 *
+	 * If all checks fail, an error gets added to prevent the topic from saving.
+	 *
+	 * @since 2.6.14
+	 *
+	 * @param int $topic_id
+	 */
+	public function validate_topic_forum_id( $topic_id = 0 ) {
+
+		// Bail if no topic
+		if ( empty( $topic_id ) ) {
+			return;
+		}
+
+		// Get current forum ID
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Bail if not a group forum
+		if ( ! bbp_is_forum_group_forum( $forum_id ) ) {
+			return;
+		}
+
+		// Get current group ID
+		$group_id = bp_get_current_group_id();
+
+		// Bail if unknown group ID
+		if ( empty( $group_id ) ) {
+			return;
+		}
+
+		// Get group forum IDs
+		$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+		// Get posted forum ID
+		$new_forum_id = ! empty( $_POST['bbp_forum_id'] )
+			? absint( $_POST['bbp_forum_id'] )
+			: 0;
+
+		// Bail if new forum ID is a forum in this group
+		if ( in_array( $new_forum_id, $forum_ids, true ) ) {
+			return;
+		}
+
+		// Get the current user ID
+		$user_id = bbp_get_current_user_id();
+
+		// Bail if current user can moderate the new forum ID
+		if ( bbp_is_user_forum_moderator( $user_id, $new_forum_id ) ) {
+			return;
+		}
+
+		// If everything else has failed, then something is wrong and we need
+		// to add an error to prevent this topic from saving.
+		bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID is invalid.', 'bbpress' ) );
+	}
+
+	/**
+	 * Validate the reply to for a reply in a group forum.
+	 *
+	 * This method ensures that when a reply to is saved, it is only allowed to
+	 * be saved to the current topic.
+	 *
+	 * If all checks fail, an error gets added to prevent the reply from saving.
+	 *
+	 * @since 2.6.14
+	 *
+	 * @param int $reply_id
+	 */
+	public function validate_reply_to_id( $reply_id = 0 ) {
+
+		// Bail if no reply
+		if ( empty( $reply_id ) ) {
+			return;
+		}
+
+		// Get posted reply to
+		$new_reply_to = ! empty( $_POST['bbp_reply_to'] )
+			? absint( $_POST['bbp_reply_to'] )
+			: 0;
+
+		// Bail if no reply to (assumes topic ID)
+		if ( empty( $new_reply_to ) ) {
+			return;
+		}
+
+		// Get current forum ID
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Bail if not a group forum
+		if ( ! bbp_is_forum_group_forum( $forum_id ) ) {
+			return;
+		}
+
+		// Get current group ID
+		$group_id = bp_get_current_group_id();
+
+		// Bail if unknown group ID
+		if ( empty( $group_id ) ) {
+			return;
+		}
+
+		// Get current topic ID
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Get topic reply IDs
+		$reply_ids = bbp_get_public_child_ids( $topic_id, bbp_get_reply_post_type() );
+
+		// Avoid recursion
+		unset( $reply_ids[ $reply_id ] );
+
+		// Bail if new reply parent ID is in this topic
+		if ( in_array( $new_reply_to, $reply_ids, true ) ) {
+			return;
+		}
+
+		// Add an error to prevent this reply from saving.
+		bbp_add_error( 'bbp_reply_to_id', __( '<strong>Error</strong>: Reply To is invalid.', 'bbpress' ) );
+	}
+
+	/** Edit ******************************************************************/
+
+	/**
+	 * Show forums and new forum form when editing a group
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @param object $group (the group to edit if in Group Admin UI)
+	 */
+	public function edit_screen( $group = false ) {
+		$forum_id  = 0;
+		$group_id  = empty( $group->id ) ? bp_get_new_group_id() : $group->id;
+		$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+		// Get the first forum ID
+		if ( ! empty( $forum_ids ) ) {
+			$forum_id = (int) is_array( $forum_ids ) ? $forum_ids[0] : $forum_ids;
+		}
+
+		// Should box be checked already?
+		$checked = is_admin() ? bp_group_is_forum_enabled( $group ) : bp_get_new_group_enable_forum() || bp_group_is_forum_enabled( bp_get_group_id() ); ?>
+
+		<h2><?php esc_html_e( 'Group Forum Settings', 'bbpress' ); ?></h2>
+
+		<fieldset>
+			<legend class="screen-reader-text"><?php esc_html_e( 'Group Forum Settings', 'bbpress' ); ?></legend>
+			<p><?php esc_html_e( 'Create a discussion forum to allow members of this group to communicate in a structured, bulletin-board style fashion.', 'bbpress' ); ?></p>
+
+			<div class="field-group">
+				<div class="checkbox">
+					<label for="bbp-edit-group-forum"><input type="checkbox" name="bbp-edit-group-forum" id="bbp-edit-group-forum" value="1"<?php checked( $checked ); ?> /> <?php esc_html_e( 'Yes. I want this group to have a forum.', 'bbpress' ); ?></label>
+				</div>
+
+				<p class="description"><?php esc_html_e( 'Saying no will not delete existing forum content.', 'bbpress' ); ?></p>
+			</div>
+
+			<?php if ( bbp_is_user_keymaster() ) : ?>
+				<div class="field-group">
+					<label for="bbp_group_forum_id"><?php esc_html_e( 'Group Forum:', 'bbpress' ); ?></label>
+					<?php
+						bbp_dropdown( array(
+							'select_id' => 'bbp_group_forum_id',
+							'show_none' => esc_html__( '&mdash; No forum &mdash;', 'bbpress' ),
+							'selected'  => $forum_id
+						) );
+					?>
+					<p class="description"><?php esc_html_e( 'Network administrators can reconfigure which forum belongs to this group.', 'bbpress' ); ?></p>
+				</div>
+			<?php endif; ?>
+
+			<?php if ( ! is_admin() ) : ?>
+				<input type="submit" value="<?php esc_attr_e( 'Save Settings', 'bbpress' ); ?>" />
+			<?php endif; ?>
+
+		</fieldset>
+
+		<?php
+
+		// Verify intent
+		if ( is_admin() ) {
+			wp_nonce_field( 'groups_edit_save_' . $this->slug, 'forum_group_admin_ui' );
+		} else {
+			wp_nonce_field( 'groups_edit_save_' . $this->slug );
+		}
+	}
+
+	/**
+	 * Save the Group Forum data on edit
+	 *
+	 * @since 2.0.0 bbPress (r3465)
+	 *
+	 * @param int $group_id (to handle Group Admin UI hook bp_group_admin_edit_after )
+	 */
+	public function edit_screen_save( $group_id = 0 ) {
+
+		// Bail if not a POST action
+		if ( ! bbp_is_post_request() ) {
+			return;
+		}
+
+		// Admin Nonce check
+		if ( is_admin() ) {
+			check_admin_referer( 'groups_edit_save_' . $this->slug, 'forum_group_admin_ui' );
+
+		// Theme-side Nonce check
+		} elseif ( ! bbp_verify_nonce_request( 'groups_edit_save_' . $this->slug ) ) {
+			bbp_add_error( 'bbp_edit_group_forum_screen_save', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+			return;
+		}
+
+		$edit_forum = ! empty( $_POST['bbp-edit-group-forum'] ) ? true : false;
+		$forum_id   = 0;
+		$group_id   = ! empty( $group_id ) ? $group_id : bp_get_current_group_id();
+
+		// Keymasters have the ability to reconfigure forums
+		if ( bbp_is_user_keymaster() ) {
+			$forum_ids = ! empty( $_POST['bbp_group_forum_id'] ) ? (array) (int) $_POST['bbp_group_forum_id'] : array();
+
+		// Use the existing forum IDs
+		} else {
+			$forum_ids = array_values( bbp_get_group_forum_ids( $group_id ) );
+		}
+
+		// Normalize group forum relationships now
+		if ( ! empty( $forum_ids ) ) {
+
+			// Loop through forums, and make sure they exist
+			foreach ( $forum_ids as $forum_id ) {
+
+				// Look for forum
+				$forum = bbp_get_forum( $forum_id );
+
+				// No forum exists, so break the relationship
+				if ( empty( $forum ) ) {
+					$this->remove_forum( array( 'forum_id' => $forum_id ) );
+					unset( $forum_ids[ $forum_id ] );
+				}
+			}
+
+			// No support for multiple forums yet
+			$forum_id = (int) ( is_array( $forum_ids )
+				? $forum_ids[0]
+				: $forum_ids );
+		}
+
+		// Update the group ID and forum ID relationships
+		bbp_update_group_forum_ids( $group_id, (array) $forum_ids );
+		bbp_update_forum_group_ids( $forum_id, (array) $group_id  );
+
+		// Update the group forum setting
+		$group = $this->toggle_group_forum( $group_id, $edit_forum );
+
+		// Create a new forum
+		if ( empty( $forum_id ) && ( true === $edit_forum ) ) {
+
+			// Set the default forum status
+			switch ( $group->status ) {
+				case 'hidden'  :
+					$status = bbp_get_hidden_status_id();
+					break;
+				case 'private' :
+					$status = bbp_get_private_status_id();
+					break;
+				case 'public'  :
+				default        :
+					$status = bbp_get_public_status_id();
+					break;
+			}
+
+			// Create the initial forum
+			$forum_id = bbp_insert_forum( array(
+				'post_parent'  => bbp_get_group_forums_root_id(),
+				'post_title'   => $group->name,
+				'post_content' => $group->description,
+				'post_status'  => $status
+			) );
+
+			// Setup forum args with forum ID
+			$new_forum_args = array( 'forum_id' => $forum_id );
+
+			// If in admin, also include the group ID
+			if ( is_admin() && ! empty( $group_id ) ) {
+				$new_forum_args['group_id'] = $group_id;
+			}
+
+			// Run the BP-specific functions for new groups
+			$this->new_forum( $new_forum_args );
+		}
+
+		// Redirect after save when not in admin
+		if ( ! is_admin() ) {
+			bp_core_redirect( trailingslashit( bp_get_group_permalink( buddypress()->groups->current_group ) . '/admin/' . $this->slug ) );
+		}
+	}
+
+	/**
+	 * Adds a meta-box to BuddyPress Group Admin UI
+	 *
+	 * @since 2.3.0 bbPress (r4814)
+	 */
+	public function group_admin_ui_edit_screen() {
+		add_meta_box(
+			'bbpress_group_admin_ui_meta_box',
+			_x( 'Discussion Forum', 'group admin edit screen', 'bbpress' ),
+			array( $this, 'group_admin_ui_display_metabox' ),
+			get_current_screen()->id,
+			'side',
+			'core'
+		);
+	}
+
+	/**
+	 * Displays the bbPress meta-box in BuddyPress Group Admin UI
+	 *
+	 * @since 2.3.0 bbPress (r4814)
+	 *
+	 * @param object $item (group object)
+	 */
+	public function group_admin_ui_display_metabox( $item ) {
+		$this->edit_screen( $item );
+	}
+
+	/** Create ****************************************************************/
+
+	/**
+	 * Show forums and new forum form when creating a group
+	 *
+	 * @since 2.0.0 bbPress (r3465)
+	 */
+	public function create_screen( $group_id = 0 ) {
+
+		// Bail if not looking at this screen
+		if ( !bp_is_group_creation_step( $this->slug ) ) {
+			return false;
+		}
+
+		// Check for possibly empty group_id
+		if ( empty( $group_id ) ) {
+			$group_id = bp_get_new_group_id();
+		}
+
+		$checked = bp_get_new_group_enable_forum() || groups_get_groupmeta( $group_id, 'forum_id' ); ?>
+
+		<h2><?php esc_html_e( 'Group Forum', 'bbpress' ); ?></h2>
+
+		<p><?php esc_html_e( 'Create a discussion forum to allow members of this group to communicate in a structured, bulletin-board style fashion.', 'bbpress' ); ?></p>
+
+		<div class="checkbox">
+			<label for="bbp-create-group-forum"><input type="checkbox" name="bbp-create-group-forum" id="bbp-create-group-forum" value="1"<?php checked( $checked ); ?> /> <?php esc_html_e( 'Yes. I want this group to have a forum.', 'bbpress' ); ?></label>
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Save the Group Forum data on create
+	 *
+	 * @since 2.0.0 bbPress (r3465)
+	 */
+	public function create_screen_save( $group_id = 0 ) {
+
+		// Nonce check
+		if ( ! bbp_verify_nonce_request( 'groups_create_save_' . $this->slug ) ) {
+			bbp_add_error( 'bbp_create_group_forum_screen_save', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+			return;
+		}
+
+		// Check for possibly empty group_id
+		if ( empty( $group_id ) ) {
+			$group_id = bp_get_new_group_id();
+		}
+
+		$create_forum = ! empty( $_POST['bbp-create-group-forum'] ) ? true : false;
+		$forum_id     = 0;
+		$forum_ids    = bbp_get_group_forum_ids( $group_id );
+
+		if ( ! empty( $forum_ids ) ) {
+			$forum_id = (int) is_array( $forum_ids ) ? $forum_ids[0] : $forum_ids;
+		}
+
+		// Create a forum, or not
+		switch ( $create_forum ) {
+			case true  :
+
+				// Bail if initial content was already created
+				if ( ! empty( $forum_id ) ) {
+					return;
+				}
+
+				// Set the default forum status
+				switch ( bp_get_new_group_status() ) {
+					case 'hidden'  :
+						$status = bbp_get_hidden_status_id();
+						break;
+					case 'private' :
+						$status = bbp_get_private_status_id();
+						break;
+					case 'public'  :
+					default        :
+						$status = bbp_get_public_status_id();
+						break;
+				}
+
+				// Create the initial forum
+				$forum_id = bbp_insert_forum( array(
+					'post_parent'  => bbp_get_group_forums_root_id(),
+					'post_title'   => bp_get_new_group_name(),
+					'post_content' => bp_get_new_group_description(),
+					'post_status'  => $status
+				) );
+
+				// Run the BP-specific functions for new groups
+				$this->new_forum( array( 'forum_id' => $forum_id ) );
+
+				// Update forum active
+				groups_update_groupmeta( bp_get_new_group_id(), '_bbp_forum_enabled_' . $forum_id, true );
+
+				// Toggle forum on
+				$this->toggle_group_forum( bp_get_new_group_id(), true );
+
+				break;
+			case false :
+
+				// Forum was created but is now being undone
+				if ( ! empty( $forum_id ) ) {
+
+					// Delete the forum
+					wp_delete_post( $forum_id, true );
+
+					// Delete meta values
+					groups_delete_groupmeta( bp_get_new_group_id(), 'forum_id' );
+					groups_delete_groupmeta( bp_get_new_group_id(), '_bbp_forum_enabled_' . $forum_id );
+
+					// Toggle forum off
+					$this->toggle_group_forum( bp_get_new_group_id(), false );
+				}
+
+				break;
+		}
+	}
+
+	/**
+	 * Used to start an output buffer
+	 *
+	 * @since 2.1.0 bbPress (r3746)
+	 */
+	public function ob_start() {
+		ob_start();
+	}
+
+	/**
+	 * Used to end an output buffer
+	 *
+	 * @since 2.1.0 bbPress (r3746)
+	 */
+	public function ob_end_clean() {
+		ob_end_clean();
+	}
+
+	/**
+	 * Creating a group forum or category (including root for group)
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 *
+	 * @param array $forum_args
+	 *
+	 * @return void if no forum_id is available
+	 */
+	public function new_forum( $forum_args = array() ) {
+
+		// Bail if no forum_id was passed
+		if ( empty( $forum_args['forum_id'] ) ) {
+			return;
+		}
+
+		// Validate forum_id
+		$forum_id = bbp_get_forum_id( $forum_args['forum_id'] );
+		$group_id = ! empty( $forum_args['group_id'] ) ? $forum_args['group_id'] : bp_get_current_group_id();
+
+		bbp_add_forum_id_to_group( $group_id, $forum_id );
+		bbp_add_group_id_to_forum( $forum_id, $group_id );
+	}
+
+	/**
+	 * Removing a group forum or category (including root for group)
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 *
+	 * @param array $forum_args
+	 *
+	 * @return void if no forum_id is available
+	 */
+	public function remove_forum( $forum_args = array() ) {
+
+		// Bail if no forum_id was passed
+		if ( empty( $forum_args['forum_id'] ) ) {
+			return;
+		}
+
+		// Validate forum_id
+		$forum_id = bbp_get_forum_id( $forum_args['forum_id'] );
+		$group_id = ! empty( $forum_args['group_id'] ) ? $forum_args['group_id'] : bp_get_current_group_id();
+
+		bbp_remove_forum_id_from_group( $group_id, $forum_id );
+		bbp_remove_group_id_from_forum( $forum_id, $group_id );
+	}
+
+	/**
+	 * Listening to BuddyPress Group deletion to remove the forum
+	 *
+	 * @since 2.3.0 bbPress (r4815)
+	 *
+	 * @param int $group_id The group ID
+	 */
+	public function disconnect_forum_from_group( $group_id = 0 ) {
+
+		// Bail if no group ID available
+		if ( empty( $group_id ) ) {
+			return;
+		}
+
+		// Get the forums for the current group
+		$forum_ids = bbp_get_group_forum_ids( $group_id );
+
+		// Bail if no forum IDs available
+		if ( empty( $forum_ids ) ) {
+			return;
+		}
+
+		// Get the first forum ID
+		$forum_id = (int) is_array( $forum_ids ) ? $forum_ids[0] : $forum_ids;
+		$this->remove_forum( array(
+			'forum_id' => $forum_id,
+			'group_id' => $group_id
+		) );
+	}
+
+	/**
+	 * Set forums' status to match the privacy status of the associated group
+	 *
+	 * Fired whenever a group is saved
+	 *
+	 * @param BP_Groups_Group $group Group object.
+	 */
+	public static function update_group_forum_visibility( BP_Groups_Group $group ) {
+
+		// Get group forum IDs
+		$forum_ids = bbp_get_group_forum_ids( $group->id );
+
+		// Bail if no forum IDs available
+		if ( empty( $forum_ids ) ) {
+			return;
+		}
+
+		// Loop through forum IDs
+		foreach ( $forum_ids as $forum_id ) {
+
+			// Get forum from ID
+			$forum = bbp_get_forum( $forum_id );
+
+			// Check for change
+			if ( $group->status !== $forum->post_status ) {
+				switch ( $group->status ) {
+
+					// Changed to hidden
+					case 'hidden' :
+						bbp_hide_forum( $forum_id, $forum->post_status );
+						break;
+
+					// Changed to private
+					case 'private' :
+						bbp_privatize_forum( $forum_id, $forum->post_status );
+						break;
+
+					// Changed to public
+					case 'public' :
+					default :
+						bbp_publicize_forum( $forum_id, $forum->post_status );
+						break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Toggle the enable_forum group setting on or off
+	 *
+	 * @since 2.3.0 bbPress (r4612)
+	 *
+	 * @param int $group_id The group to toggle
+	 * @param bool $enabled True for on, false for off
+	 * @return False if group is not found, otherwise return the group
+	 */
+	public function toggle_group_forum( $group_id = 0, $enabled = false ) {
+
+		// Get the group
+		$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+		// Bail if group cannot be found
+		if ( empty( $group ) ) {
+			return false;
+		}
+
+		// Set forum enabled status
+		$group->enable_forum = (int) $enabled;
+
+		// Save the group
+		$group->save();
+
+		// Maybe disconnect forum from group
+		if ( empty( $enabled ) ) {
+			$this->disconnect_forum_from_group( $group_id );
+		}
+
+		// Update internal private and forum ID variables
+		bbp_repair_forum_visibility();
+
+		// Return the group
+		return $group;
+	}
+
+	/** Display Methods *******************************************************/
+
+	/**
+	 * Output the forums for a group in the edit screens
+	 *
+	 * As of right now, bbPress only supports 1-to-1 group forum relationships.
+	 * In the future, many-to-many should be allowed.
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 */
+	public function display_forums( $offset = 0 ) {
+
+		// Allow actions immediately before group forum output
+		do_action( 'bbp_before_group_forum_display' );
+
+		// Load up bbPress once
+		$bbp = bbpress();
+
+		/** Query Resets ******************************************************/
+
+		// Forum data
+		$forum_action = bp_action_variable( $offset );
+		$forum_ids    = bbp_get_group_forum_ids( bp_get_current_group_id() );
+		$forum_id     = array_shift( $forum_ids );
+
+		// Always load up the group forum
+		bbp_has_forums( array(
+			'p'           => $forum_id,
+			'post_parent' => null
+		) );
+
+		// Set the global forum ID
+		$bbp->current_forum_id = $forum_id;
+
+		// Assume forum query
+		bbp_set_query_name( 'bbp_single_forum' ); ?>
+
+		<div id="bbpress-forums" class="bbpress-wrapper">
+
+			<?php switch ( $forum_action ) :
+
+				/** Single Forum **********************************************/
+
+				case false  :
+				case 'page' :
+
+					// Strip the super stickies from topic query
+					add_filter( 'bbp_get_super_stickies', array( $this, 'no_super_stickies'  ), 10, 1 );
+
+					// Unset the super sticky option on topic form
+					add_filter( 'bbp_get_topic_types',    array( $this, 'unset_super_sticky' ), 10, 1 );
+
+					// Query forums and show them if they exist
+					if ( bbp_forums() ) :
+
+						// Setup the forum
+						bbp_the_forum();
+
+						// Only wrap title in H2 if not empty
+						$title = bbp_get_forum_title();
+						if ( ! empty( $title ) ) {
+							echo '<h2>' . $title . '</h2>';
+						}
+
+						// Output forum
+						bbp_get_template_part( 'content', 'single-forum' );
+
+					// No forums found
+					else : ?>
+
+						<div id="message" class="info">
+							<p><?php esc_html_e( 'This group does not currently have a forum.', 'bbpress' ); ?></p>
+						</div>
+
+					<?php endif;
+
+					break;
+
+				/** Single Topic **********************************************/
+
+				case $this->topic_slug :
+
+					// hide the 'to front' admin links
+					add_filter( 'bbp_get_topic_stick_link', array( $this, 'hide_super_sticky_admin_link' ), 10, 2 );
+
+					// Get the topic
+					bbp_has_topics( array(
+						'name'           => bp_action_variable( $offset + 1 ),
+						'posts_per_page' => 1,
+						'show_stickies'  => false
+					) );
+
+					// If no topic, 404
+					if ( ! bbp_topics() ) {
+						bp_do_404( bbp_get_forum_permalink( $forum_id ) );
+
+						// Only wrap title in H2 if not empty
+						$title = bbp_get_forum_title();
+						if ( ! empty( $title ) ) {
+							echo '<h2>' . $title . '</h2>';
+						}
+
+						// No topics
+						bbp_get_template_part( 'feedback', 'no-topics' );
+						break;
+					}
+
+					// Setup the topic
+					bbp_the_topic();
+
+					// Only wrap title in H2 if not empty
+					$title = bbp_get_topic_title();
+					if ( ! empty( $title ) ) {
+						echo '<h2>' . $title . '</h2>';
+					}
+
+					// Topic edit
+					if ( bp_action_variable( $offset + 2 ) === bbp_get_edit_slug() ) :
+
+						// Unset the super sticky link on edit topic template
+						add_filter( 'bbp_get_topic_types', array( $this, 'unset_super_sticky' ), 10, 1 );
+
+						// Get the main query object
+						$wp_query = bbp_get_wp_query();
+
+						// Set the edit switches
+						$wp_query->bbp_is_edit       = true;
+						$wp_query->bbp_is_topic_edit = true;
+
+						// Setup the global forum ID
+						$bbp->current_topic_id       = get_the_ID();
+
+						// Merge
+						if ( ! empty( $_GET['action'] ) && 'merge' === $_GET['action'] ) :
+							bbp_set_query_name( 'bbp_topic_merge' );
+							bbp_get_template_part( 'form', 'topic-merge' );
+
+						// Split
+						elseif ( ! empty( $_GET['action'] ) && 'split' === $_GET['action'] ) :
+							bbp_set_query_name( 'bbp_topic_split' );
+							bbp_get_template_part( 'form', 'topic-split' );
+
+						// Edit
+						else :
+							bbp_set_query_name( 'bbp_topic_form' );
+							bbp_get_template_part( 'form', 'topic' );
+
+						endif;
+
+					// Single Topic
+					else :
+						bbp_set_query_name( 'bbp_single_topic' );
+						bbp_get_template_part( 'content', 'single-topic' );
+					endif;
+					break;
+
+				/** Single Reply **********************************************/
+
+				case $this->reply_slug :
+
+					// Get the reply
+					bbp_has_replies( array(
+						'name'           => bp_action_variable( $offset + 1 ),
+						'posts_per_page' => 1
+					) );
+
+					// If no topic, 404
+					if ( ! bbp_replies() ) {
+						bp_do_404( bbp_get_forum_permalink( $forum_id ) );
+
+						// Only wrap title in H2 if not empty
+						$title = bbp_get_forum_title();
+						if ( ! empty( $title ) ) {
+							echo '<h2>' . $title . '</h2>';
+						}
+
+						// No replies
+						bbp_get_template_part( 'feedback', 'no-replies' );
+						break;
+					}
+
+					// Setup the reply
+					bbp_the_reply();
+
+					// Only wrap title in H2 if not empty
+					$title = bbp_get_reply_title();
+					if ( ! empty( $title ) ) {
+						echo '<h2>' . $title . '</h2>';
+					}
+
+					if ( bp_action_variable( $offset + 2 ) === bbp_get_edit_slug() ) :
+
+						// Get the main query object
+						$wp_query = bbp_get_wp_query();
+
+						// Set the edit switches
+						$wp_query->bbp_is_edit       = true;
+						$wp_query->bbp_is_reply_edit = true;
+
+						// Setup the global reply ID
+						$bbp->current_reply_id       = get_the_ID();
+
+						// Move
+						if ( ! empty( $_GET['action'] ) && ( 'move' === $_GET['action'] ) ) :
+							bbp_set_query_name( 'bbp_reply_move' );
+							bbp_get_template_part( 'form', 'reply-move' );
+
+						// Edit
+						else :
+							bbp_set_query_name( 'bbp_reply_form' );
+							bbp_get_template_part( 'form', 'reply' );
+						endif;
+					endif;
+					break;
+			endswitch;
+
+			// Reset the query
+			wp_reset_query(); ?>
+
+		</div><!-- #bbpress-forums -->
+
+		<?php
+
+		// Allow actions immediately after group forum output
+		do_action( 'bbp_after_group_forum_display' );
+	}
+
+	/** Super sticky filters ***************************************************/
+
+	/**
+	 * Strip super stickies from the topic query
+	 *
+	 * @since 2.3.0 bbPress (r4810)
+	 *
+	 * @access private
+	 * @param array $super the super sticky post ID's
+	 * @return array (empty)
+	 */
+	public function no_super_stickies( $super = array() ) {
+		$super = array();
+		return $super;
+	}
+
+	/**
+	 * Unset the type super sticky from topic type
+	 *
+	 * @since 2.3.0 bbPress (r4810)
+	 *
+	 * @access private
+	 * @param array $args
+	 * @return array $args without the to-front link
+	 */
+	public function unset_super_sticky( $args = array() ) {
+		if ( isset( $args['super'] ) ) {
+			unset( $args['super'] );
+		}
+		return $args;
+	}
+
+	/**
+	 * Ugly preg_replace to hide the to front admin link
+	 *
+	 * @since 2.3.0 bbPress (r4810)
+	 *
+	 * @access private
+	 * @param string $retval
+	 * @param array $args
+	 * @return string $retval without the to-front link
+	 */
+	public function hide_super_sticky_admin_link( $retval = '', $args = array() ) {
+		if ( strpos( $retval, '(' ) ) {
+			$retval = preg_replace( '/(\(.+?)+(\))/i', '', $retval );
+		}
+
+		return $retval;
+	}
+
+	/** Redirect Helpers ******************************************************/
+
+	/**
+	 * Redirect to the group forum screen
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 *
+	 * @param str $redirect_url
+	 * @param str $redirect_to
+	 */
+	public function new_topic_redirect_to( $redirect_url = '', $redirect_to = '', $topic_id = 0 ) {
+		if ( bp_is_group() ) {
+			$topic        = bbp_get_topic( $topic_id );
+			$topic_hash   = '#post-' . $topic_id;
+			$redirect_url = trailingslashit( bp_get_group_permalink( groups_get_current_group() ) ) . trailingslashit( $this->slug ) . trailingslashit( $this->topic_slug ) . trailingslashit( $topic->post_name ) . $topic_hash;
+		}
+
+		return $redirect_url;
+	}
+
+	/**
+	 * Redirect to the group forum screen
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 */
+	public function new_reply_redirect_to( $redirect_url = '', $redirect_to = '', $reply_id = 0 ) {
+
+		if ( bp_is_group() ) {
+			$topic_id       = bbp_get_reply_topic_id( $reply_id );
+			$topic          = bbp_get_topic( $topic_id );
+			$reply_position = bbp_get_reply_position( $reply_id, $topic_id );
+			$reply_page     = ceil( (int) $reply_position / (int) bbp_get_replies_per_page() );
+			$reply_hash     = '#post-' . $reply_id;
+			$topic_url      = trailingslashit( bp_get_group_permalink( groups_get_current_group() ) ) . trailingslashit( $this->slug ) . trailingslashit( $this->topic_slug ) . trailingslashit( $topic->post_name );
+
+			// Don't include pagination if on first page
+			if ( 1 >= $reply_page ) {
+				$redirect_url = trailingslashit( $topic_url ) . $reply_hash;
+
+			// Include pagination
+			} else {
+				$redirect_url = trailingslashit( $topic_url ) . trailingslashit( bbp_get_paged_slug() ) . trailingslashit( $reply_page ) . $reply_hash;
+			}
+
+			// Add topic view query arg back to end if it is set
+			if ( bbp_get_view_all() ) {
+				$redirect_url = bbp_add_view_all( $redirect_url );
+			}
+		}
+
+		return $redirect_url;
+	}
+
+	/**
+	 * Redirect to the group admin forum edit screen
+	 *
+	 * @since 2.1.0 bbPress (r3653)
+	 */
+	public function edit_redirect_to( $redirect_url = '' ) {
+
+		// Get the current group, if there is one
+		$group = groups_get_current_group();
+
+		// If this is a group of any kind, empty out the redirect URL
+		if ( bp_is_group_admin_screen( $this->slug ) ) {
+			$redirect_url = trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() . '/' . $group->slug . '/admin/' . $this->slug );
+		}
+
+		return $redirect_url;
+	}
+
+	/** Form Helpers **********************************************************/
+
+	/**
+	 * Make Forum Parent a hidden field instead of a selectable one.
+	 *
+	 * @since 2.1.0 bbPress (r3746)
+	 */
+	public function forum_parent() {
+	?>
+
+		<input type="hidden" name="bbp_forum_parent_id" id="bbp_forum_parent_id" value="<?php bbp_group_forums_root_id(); ?>" />
+
+	<?php
+	}
+
+	/**
+	 * Output a dropdown for picking which group forum this topic is for.
+	 *
+	 * @since 2.1.0 bbPress (r3746)
+	 */
+	public function topic_parent() {
+
+		// Get the group ID
+		$gid       = bp_get_current_group_id();
+
+		// Get the forum IDs for this group
+		$forum_ids = bbp_get_group_forum_ids( $gid );
+
+		// Attempt to get the current topic forum ID
+		$topic_id  = bbp_get_topic_id();
+		$forum_id  = bbp_get_topic_forum_id( $topic_id );
+
+		// Setup the query arguments - note that these may be overridden later
+		// by various bbPress visibility and capability filters.
+		$args = array(
+			'post_type'   => bbp_get_forum_post_type(),
+			'post_status' => bbp_get_public_status_id(),
+			'include'     => $forum_ids,
+			'numberposts' => -1,
+			'orderby'     => 'menu_order',
+			'order'       => 'ASC',
+		);
+
+		// Get the forum objects for these forum IDs
+		$forums = get_posts( $args );
+
+		// Setup the dropdown arguments
+		$dd_args = array(
+			'posts'    => $forums,
+			'selected' => $forum_id,
+		); ?>
+
+		<p>
+			<label for="bbp_forum_id"><?php esc_html_e( 'Forum:', 'bbpress' ); ?></label><br />
+			<?php bbp_dropdown( $dd_args ); ?>
+		</p>
+
+	<?php
+	}
+
+	/**
+	 * Permissions to view the 'New Topic'/'Reply To' form in a BuddyPress group.
+	 *
+	 * @since 2.3.0 bbPress (r4608)
+	 *
+	 * @param bool $retval Are we allowed to view the reply form?
+	 *
+	 * @return bool
+	 */
+	public function form_permissions( $retval = false ) {
+
+		// Bail if user is not logged in
+		if ( ! is_user_logged_in() ) {
+			return $retval;
+
+		// Keymasters can always pass go
+		} elseif ( bbp_is_user_keymaster() ) {
+			$retval = true;
+
+		// Non-members cannot see forms
+		} elseif ( ! bbp_group_is_member() ) {
+			$retval = false;
+
+		// Banned users cannot see forms
+		} elseif ( bbp_group_is_banned() ) {
+			$retval = false;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Add a hidden input field on the group settings page if the group forum is
+	 * enabled.
+	 *
+	 * Due to the way BuddyPress' group admin settings page saves its settings,
+	 * we need to let BP know that bbPress added a forum.
+	 *
+	 * @since 2.4.0 bbPress (r5026)
+	 *
+	 * @link https://bbpress.trac.wordpress.org/ticket/2339/
+	 * @see groups_screen_group_admin_settings()
+	 */
+	public function group_settings_hidden_field() {
+
+		// if a forum is not enabled, we don't need to add this field
+		if ( ! bp_group_is_forum_enabled() ) {
+			return;
+		} ?>
+
+		<input type="hidden" name="group-show-forum" id="group-show-forum" value="1" />
+
+	<?php
+	}
+
+	/** Permalink Mappers *****************************************************/
+
+	/**
+	 * Maybe map a bbPress forum/topic/reply permalink to the corresponding group
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param int $post_id
+	 * @return Bail early if not a group forum post
+	 * @return string
+	 */
+	private function maybe_map_permalink_to_group( $post_id = 0, $url = false ) {
+
+		switch ( get_post_type( $post_id ) ) {
+
+			// Reply
+			case bbp_get_reply_post_type() :
+				$topic_id = bbp_get_reply_topic_id( $post_id );
+				$forum_id = bbp_get_reply_forum_id( $post_id );
+				$url_end  = trailingslashit( $this->reply_slug ) . get_post_field( 'post_name', $post_id );
+				break;
+
+			// Topic
+			case bbp_get_topic_post_type() :
+				$topic_id = $post_id;
+				$forum_id = bbp_get_topic_forum_id( $post_id );
+				$url_end  = trailingslashit( $this->topic_slug ) . get_post_field( 'post_name', $post_id );
+				break;
+
+			// Forum
+			case bbp_get_forum_post_type() :
+				$forum_id = $post_id;
+				$url_end  = ''; //get_post_field( 'post_name', $post_id );
+				break;
+
+			// Unknown
+			default :
+				return $url;
+		}
+
+		// Get group ID's for this forum
+		$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+		// Bail if the post isn't associated with a group
+		if ( empty( $group_ids ) ) {
+			return $url;
+		}
+
+		// @todo Multiple group forums/forum groups
+		$group_id = $group_ids[0];
+		$group    = groups_get_group( array( 'group_id' => $group_id ) );
+
+		if ( bp_is_group_admin_screen( $this->slug ) ) {
+			$group_permalink = trailingslashit( bp_get_group_admin_permalink( $group ) );
+		} else {
+			$group_permalink = trailingslashit( bp_get_group_permalink( $group ) );
+		}
+
+		return trailingslashit( trailingslashit( $group_permalink . $this->slug ) . $url_end );
+	}
+
+	/**
+	 * Map a forum permalink to its corresponding group
+	 *
+	 * @since 2.1.0 bbPress (r3802)
+	 *
+	 * @param string $url
+	 * @param int $forum_id
+	 * @return string
+	 */
+	public function map_forum_permalink_to_group( $url, $forum_id ) {
+		return $this->maybe_map_permalink_to_group( $forum_id, $url );
+	}
+
+	/**
+	 * Map a topic permalink to its group forum
+	 *
+	 * @since 2.1.0 bbPress (r3802)
+	 *
+	 * @param string $url
+	 * @param int $topic_id
+	 * @return string
+	 */
+	public function map_topic_permalink_to_group( $url, $topic_id ) {
+		return $this->maybe_map_permalink_to_group( $topic_id, $url );
+	}
+
+	/**
+	 * Map a reply permalink to its group forum
+	 *
+	 * @since 2.1.0 bbPress (r3802)
+	 *
+	 * @param string $url
+	 * @param int $reply_id
+	 * @return string
+	 */
+	public function map_reply_permalink_to_group( $url, $reply_id ) {
+		return $this->maybe_map_permalink_to_group( bbp_get_reply_topic_id( $reply_id ), $url );
+	}
+
+	/**
+	 * Map a reply edit link to its group forum
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param string $url
+	 * @param int $reply_id
+	 * @return string
+	 */
+	public function map_reply_edit_url_to_group( $url, $reply_id ) {
+		$new = $this->maybe_map_permalink_to_group( $reply_id );
+
+		if ( empty( $new ) ) {
+			return $url;
+		}
+
+		return trailingslashit( $new ) . bbpress()->edit_id  . '/';
+	}
+
+	/**
+	 * Map a post link to its group forum
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param string $url
+	 * @param obj $post
+	 * @param boolean $leavename
+	 * @return string
+	 */
+	public function post_link( $url, $post ) {
+		return $this->maybe_map_permalink_to_group( $post->ID, $url );
+	}
+
+	/**
+	 * Map a page link to its group forum
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param string $url
+	 * @param int $post_id
+	 * @param $sample
+	 * @return string
+	 */
+	public function page_link( $url, $post_id ) {
+		return $this->maybe_map_permalink_to_group( $post_id, $url );
+	}
+
+	/**
+	 * Map a custom post type link to its group forum
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param string $url
+	 * @param obj $post
+	 * @param $leavename
+	 * @param $sample
+	 * @return string
+	 */
+	public function post_type_link( $url, $post ) {
+		return $this->maybe_map_permalink_to_group( $post->ID, $url );
+	}
+
+	/**
+	 * Fix pagination of topics on forum view
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param array $args
+	 * @return array
+	 */
+	public function topic_pagination( $args ) {
+		$new = $this->maybe_map_permalink_to_group( bbp_get_forum_id() );
+
+		if ( empty( $new ) ) {
+			return $args;
+		}
+
+		$args['base'] = trailingslashit( $new ) . bbp_get_paged_slug() . '/%#%/';
+
+		return $args;
+	}
+
+	/**
+	 * Fix pagination of replies on topic view
+	 *
+	 * @since 2.2.0 bbPress (r4266)
+	 *
+	 * @param array $args
+	 * @return array
+	 */
+	public function replies_pagination( $args ) {
+		$new = $this->maybe_map_permalink_to_group( bbp_get_topic_id() );
+		if ( empty( $new ) ) {
+			return $args;
+		}
+
+		$args['base'] = trailingslashit( $new ) . bbp_get_paged_slug() . '/%#%/';
+
+		return $args;
+	}
+
+	/**
+	 * Ensure that forum content associated with a BuddyPress group can only be
+	 * viewed via the group URL.
+	 *
+	 * @since 2.1.0 bbPress (r3802)
+	 */
+	public function redirect_canonical() {
+
+		// Bail if on a RSS feed
+		if ( is_feed() ) {
+			return;
+		}
+
+		// Viewing a single forum
+		if ( bbp_is_single_forum() ) {
+			$forum_id  = get_the_ID();
+			$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+		// Viewing a single topic
+		} elseif ( bbp_is_single_topic() ) {
+			$topic_id  = get_the_ID();
+			$slug      = get_post_field( 'post_name', $topic_id );
+			$forum_id  = bbp_get_topic_forum_id( $topic_id );
+			$group_ids = bbp_get_forum_group_ids( $forum_id );
+
+		// Not a forum or topic
+		} else {
+			return;
+		}
+
+		// Bail if not a group forum
+		if ( empty( $group_ids ) ) {
+			return;
+		}
+
+		// Use the first group ID
+		$group_id 	 = $group_ids[0];
+		$group    	 = groups_get_group( array( 'group_id' => $group_id ) );
+		$group_link  = trailingslashit( bp_get_group_permalink( $group ) );
+		$redirect_to = trailingslashit( $group_link . $this->slug );
+
+		// Add topic slug to URL
+		if ( bbp_is_single_topic() ) {
+			$redirect_to  = trailingslashit( $redirect_to . $this->topic_slug . '/' . $slug );
+		}
+
+		bp_core_redirect( $redirect_to );
+	}
+
+	/** Activity **************************************************************/
+
+	/**
+	 * Map a forum post to its corresponding group in the group activity stream.
+	 *
+	 * @since 2.2.0 bbPress (r4396)
+	 *
+	 * @param array $args Arguments from BBP_BuddyPress_Activity::record_activity()
+	 *
+	 * @return array
+	 */
+	public function map_activity_to_group( $args = array() ) {
+
+		// Get current BP group
+		$group = groups_get_current_group();
+
+		// Not posting from a BuddyPress group? stop now!
+		if ( empty( $group ) ) {
+			return $args;
+		}
+
+		// Set the component to 'groups' so the activity item shows up in the group
+		$args['component']         = buddypress()->groups->id;
+
+		// Move the forum post ID to the secondary item ID
+		$args['secondary_item_id'] = $args['item_id'];
+
+		// Set the item ID to the group ID so the activity item shows up in the group
+		$args['item_id']           = $group->id;
+
+		// Update the group's last activity
+		groups_update_last_activity( $group->id );
+
+		return $args;
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/extend/buddypress/index.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/extend/buddypress/loader.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/loader.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/loader.php	(revision 7162)
@@ -0,0 +1,389 @@
+<?php
+
+/**
+ * bbPress BuddyPress Component Class
+ *
+ * bbPress and BuddyPress are designed to connect together seamlessly and
+ * invisibly, and this is the hunk of code necessary to make that happen.
+ *
+ * The code in this BuddyPress Extension does some pretty complicated stuff,
+ * far outside the realm of the simplicity bbPress is traditionally known for.
+ *
+ * While the rest of bbPress serves as an example of how to write pretty, simple
+ * code, what's in these files is pure madness. It should not be used as an
+ * example of anything other than successfully juggling chainsaws and puppy-dogs.
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Forums_Component' ) ) :
+/**
+ * Loads Forums Component
+ *
+ * @since 2.1.0 bbPress (r3552)
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+class BBP_Forums_Component extends BP_Component {
+
+	/**
+	 * Start the forums component creation process
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function __construct() {
+		parent::start(
+			'forums',
+			esc_html__( 'Forums', 'bbpress' ),
+			bbpress()->includes_dir . 'extend/buddypress/'
+		);
+		$this->includes();
+		$this->setup_globals();
+		$this->setup_actions();
+		$this->fully_loaded();
+	}
+
+	/**
+	 * Include BuddyPress classes and functions
+	 */
+	public function includes( $includes = array() ) {
+
+		// Helper BuddyPress functions
+		$includes[] = 'functions.php';
+
+		// Members modifications
+		$includes[] = 'members.php';
+
+		// BuddyPress Notfications Extension functions
+		if ( bp_is_active( 'notifications' ) ) {
+			$includes[] = 'notifications.php';
+		}
+
+		// BuddyPress Activity Extension class
+		if ( bp_is_active( 'activity' ) ) {
+			$includes[] = 'activity.php';
+		}
+
+		// BuddyPress Group Extension class
+		if ( bbp_is_group_forums_active() && bp_is_active( 'groups' ) ) {
+			$includes[] = 'groups.php';
+		}
+
+		// Require files if they exist
+		foreach ( $includes as $file ) {
+			if ( @is_file( $this->path . $file ) ) {
+				require $this->path . $file;
+			}
+		}
+
+		/**
+		 * Hook for plugins to include files, if necessary.
+		 *
+		 * @since 2.6.0 bbPress (r3552)
+		 */
+		do_action( "bp_{$this->id}_includes" );
+	}
+
+	/**
+	 * Setup globals
+	 *
+	 * The BP_FORUMS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define the parent forum ID
+		if ( ! defined( 'BP_FORUMS_PARENT_FORUM_ID' ) ) {
+			define( 'BP_FORUMS_PARENT_FORUM_ID', 1 );
+		}
+
+		// Define a slug, if necessary
+		if ( ! defined( 'BP_FORUMS_SLUG' ) ) {
+			define( 'BP_FORUMS_SLUG', $this->id );
+		}
+
+		// All arguments for forums component
+		$args = array(
+			'path'          => BP_PLUGIN_DIR,
+			'slug'          => BP_FORUMS_SLUG,
+			'root_slug'     => isset( $bp->pages->forums->slug ) ? $bp->pages->forums->slug : BP_FORUMS_SLUG,
+			'has_directory' => false,
+			'search_string' => esc_html__( 'Search Forums...', 'bbpress' ),
+		);
+
+		parent::setup_globals( $args );
+	}
+
+	/**
+	 * Setup the actions
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 *
+	 * @access private
+	 * @link https://bbpress.trac.wordpress.org/ticket/2176
+	 */
+	public function setup_actions() {
+
+		// Setup the components
+		add_action( 'bp_init', array( $this, 'setup_components' ), 7 );
+
+		parent::setup_actions();
+	}
+
+	/**
+	 * Instantiate classes for BuddyPress integration
+	 *
+	 * @since 2.0.0 bbPress (r3395)
+	 */
+	public function setup_components() {
+
+		// Always load the members component
+		bbpress()->extend->buddypress->members = new BBP_BuddyPress_Members();
+
+		// Create new activity class
+		if ( bp_is_active( 'activity' ) ) {
+			bbpress()->extend->buddypress->activity = new BBP_BuddyPress_Activity();
+		}
+
+		// Register the group extension only if groups are active
+		if ( bbp_is_group_forums_active() && bp_is_active( 'groups' ) ) {
+			bp_register_group_extension( 'BBP_Forums_Group_Extension' );
+		}
+	}
+
+	/**
+	 * Allow the variables, actions, and filters to be modified by third party
+	 * plugins and themes.
+	 *
+	 * @since 2.1.0 bbPress (r3902)
+	 */
+	private function fully_loaded() {
+		do_action_ref_array( 'bbp_buddypress_loaded', array( $this ) );
+	}
+
+	/**
+	 * Setup BuddyBar navigation
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Stop if there is no user displayed or logged in
+		if ( ! is_user_logged_in() && !bp_displayed_user_id() ) {
+			return;
+		}
+
+		// Define local variable(s)
+		$user_domain = '';
+
+		// Add 'Forums' to the main navigation
+		$main_nav = array(
+			'name'                => esc_html__( 'Forums', 'bbpress' ),
+			'slug'                => $this->slug,
+			'position'            => 80,
+			'screen_function'     => 'bbp_member_forums_screen_topics',
+			'default_subnav_slug' => bbp_get_topic_archive_slug(),
+			'item_css_id'         => $this->id
+		);
+
+		// Determine user to use
+		if ( bp_displayed_user_id() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		// User link
+		$forums_link = trailingslashit( $user_domain . $this->slug );
+
+		// Topics started
+		$sub_nav[] = array(
+			'name'            => esc_html__( 'Topics Started', 'bbpress' ),
+			'slug'            => bbp_get_topic_archive_slug(),
+			'parent_url'      => $forums_link,
+			'parent_slug'     => $this->slug,
+			'screen_function' => 'bbp_member_forums_screen_topics',
+			'position'        => 20,
+			'item_css_id'     => 'topics'
+		);
+
+		// Replies to topics
+		$sub_nav[] = array(
+			'name'            => esc_html__( 'Replies Created', 'bbpress' ),
+			'slug'            => bbp_get_reply_archive_slug(),
+			'parent_url'      => $forums_link,
+			'parent_slug'     => $this->slug,
+			'screen_function' => 'bbp_member_forums_screen_replies',
+			'position'        => 40,
+			'item_css_id'     => 'replies'
+		);
+
+		// Engagements
+		if ( bbp_is_engagements_active() ) {
+			$sub_nav[] = array(
+				'name'            => esc_html__( 'Engagements', 'bbpress' ),
+				'slug'            => bbp_get_user_engagements_slug(),
+				'parent_url'      => $forums_link,
+				'parent_slug'     => $this->slug,
+				'screen_function' => 'bbp_member_forums_screen_engagements',
+				'position'        => 60,
+				'item_css_id'     => 'engagements'
+			);
+		}
+
+		// Favorite topics
+		if ( bbp_is_favorites_active() ){
+			$sub_nav[] = array(
+				'name'            => esc_html__( 'Favorites', 'bbpress' ),
+				'slug'            => bbp_get_user_favorites_slug(),
+				'parent_url'      => $forums_link,
+				'parent_slug'     => $this->slug,
+				'screen_function' => 'bbp_member_forums_screen_favorites',
+				'position'        => 80,
+				'item_css_id'     => 'favorites'
+			);
+		}
+
+		// Subscribed topics (my profile only)
+		if ( bp_is_my_profile() && bbp_is_subscriptions_active() ) {
+			$sub_nav[] = array(
+				'name'            => esc_html__( 'Subscriptions', 'bbpress' ),
+				'slug'            => bbp_get_user_subscriptions_slug(),
+				'parent_url'      => $forums_link,
+				'parent_slug'     => $this->slug,
+				'screen_function' => 'bbp_member_forums_screen_subscriptions',
+				'position'        => 100,
+				'item_css_id'     => 'subscriptions'
+			);
+		}
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the admin bar
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user
+		if ( is_user_logged_in() ) {
+
+			// If BuddyPress is network activated and bbPress is
+			// not activated on a the root blog but on any child one
+			if ( ! bp_is_root_blog() ) {
+				$user_id               = bbp_get_current_user_id();
+				$my_account_link       = bbp_get_user_profile_url( $user_id );
+				$my_topics_link        = bbp_get_user_topics_created_url( $user_id );
+				$my_replies_link       = bbp_get_user_replies_created_url( $user_id );
+				$my_engagements_link   = bbp_get_user_engagements_url( $user_id );
+				$my_favorites_link     = bbp_get_favorites_permalink( $user_id );
+				$my_subscriptions_link = bbp_get_subscriptions_permalink( $user_id );
+			} else {
+
+				// Setup the logged in user variables
+				$user_domain = bp_loggedin_user_domain();
+				$forums_link = trailingslashit( $user_domain . $this->slug );
+
+				$my_account_link       = trailingslashit( $forums_link );
+				$my_topics_link        = trailingslashit( $forums_link . bbp_get_topic_archive_slug() );
+				$my_replies_link       = trailingslashit( $forums_link . bbp_get_reply_archive_slug() );
+				$my_engagements_link   = trailingslashit( $forums_link . bbp_get_user_engagements_slug() );
+				$my_favorites_link     = trailingslashit( $forums_link . bbp_get_user_favorites_slug() );
+				$my_subscriptions_link = trailingslashit( $forums_link . bbp_get_user_subscriptions_slug() );
+			}
+
+			// Add the "My Account" sub menus
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => esc_html__( 'Forums', 'bbpress' ),
+				'href'   => $my_account_link
+			);
+
+			// Topics
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-topics',
+				'title'  => esc_html__( 'Topics Started', 'bbpress' ),
+				'href'   => $my_topics_link
+			);
+
+			// Replies
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-replies',
+				'title'  => esc_html__( 'Replies Created', 'bbpress' ),
+				'href'   => $my_replies_link
+			);
+
+			// Engagements
+			if ( bbp_is_engagements_active() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-engagements',
+					'title'  => esc_html__( 'Engagements', 'bbpress' ),
+					'href'   => $my_engagements_link
+				);
+			}
+
+			// Favorites
+			if ( bbp_is_favorites_active() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-favorites',
+					'title'  => esc_html__( 'Favorite Topics', 'bbpress' ),
+					'href'   => $my_favorites_link
+				);
+			}
+
+			// Subscriptions
+			if ( bbp_is_subscriptions_active() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-subscriptions',
+					'title'  => esc_html__( 'Subscribed Topics', 'bbpress' ),
+					'href'   => $my_subscriptions_link
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Sets up the title for pages and <title>
+	 *
+	 * @since 2.1.0 bbPress (r3552)
+	 */
+	public function setup_title() {
+		$bp = buddypress();
+
+		// Adjust title based on view
+		if ( bp_is_forums_component() ) {
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = esc_html__( 'Forums', 'bbpress' );
+			} elseif ( bp_is_user() ) {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb'
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/extend/buddypress/members.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/members.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/members.php	(revision 7162)
@@ -0,0 +1,241 @@
+<?php
+
+/**
+ * bbPress BuddyPress Members Class
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'BBP_Forums_Members' ) ) :
+/**
+ * Member profile modifications
+ *
+ * @since 2.2.0 bbPress (r4395)
+ * @since 2.6.0 bbPress (r6320) Add engagements support
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+class BBP_BuddyPress_Members {
+
+	/**
+	 * Main constructor for modifying bbPress profile links
+	 *
+	 * @since 2.2.0 bbPress (r4395)
+	 */
+	public function __construct() {
+		$this->setup_actions();
+		$this->setup_filters();
+		$this->fully_loaded();
+	}
+
+	/**
+	 * Setup the actions
+	 *
+	 * @since 2.2.0 bbPress (r4395)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		// Allow unsubscribe/unfavorite links to work
+		add_action( 'bp_template_redirect', array( $this, 'set_member_forum_query_vars' ) );
+
+		/** Favorites *********************************************************/
+
+		// Move handler to 'bp_actions' - BuddyPress bypasses template_loader
+		remove_action( 'bbp_get_request', 'bbp_favorites_handler', 1 );
+		add_action(    'bp_actions',      'bbp_favorites_handler', 1 );
+
+		/** Subscriptions *****************************************************/
+
+		// Move handler to 'bp_actions' - BuddyPress bypasses template_loader
+		remove_action( 'bbp_get_request', 'bbp_subscriptions_handler', 1 );
+		add_action(    'bp_actions',      'bbp_subscriptions_handler', 1 );
+	}
+
+	/**
+	 * Setup the filters
+	 *
+	 * @since 2.2.0 bbPress (r4395)
+	 * @since 2.6.0 bbPress (r6320) Add engagements support
+	 *
+	 * @access private
+	 */
+	private function setup_filters() {
+		add_filter( 'bbp_pre_get_user_profile_url',         array( $this, 'get_user_profile_url'        ) );
+		add_filter( 'bbp_pre_get_user_topics_created_url',  array( $this, 'get_topics_created_url'      ) );
+		add_filter( 'bbp_pre_get_user_replies_created_url', array( $this, 'get_replies_created_url'     ) );
+		add_filter( 'bbp_pre_get_user_engagements_url',     array( $this, 'get_engagements_permalink'   ) );
+		add_filter( 'bbp_pre_get_favorites_permalink',      array( $this, 'get_favorites_permalink'     ) );
+		add_filter( 'bbp_pre_get_subscriptions_permalink',  array( $this, 'get_subscriptions_permalink' ) );
+	}
+
+	/**
+	 * Allow the variables, actions, and filters to be modified by third party
+	 * plugins and themes.
+	 *
+	 * @since 2.6.0 bbPress (r6808)
+	 */
+	private function fully_loaded() {
+		do_action_ref_array( 'bbp_buddypress_members_loaded', array( $this ) );
+	}
+
+	/** Filters ***************************************************************/
+
+	/**
+	 * Override bbPress profile URL with BuddyPress profile URL
+	 *
+	 * @since 2.0.0 bbPress (r3401)
+	 * @since 2.6.0 bbPress (r6320) Add engagements support
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_user_profile_url( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id );
+	}
+
+	/**
+	 * Override bbPress topics created URL with BuddyPress profile URL
+	 *
+	 * @since 2.6.0 bbPress (r3721)
+	 * @since 2.6.0 bbPress (r6803) Use private method
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_topics_created_url( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id, bbp_get_topic_archive_slug() );
+	}
+
+	/**
+	 * Override bbPress replies created URL with BuddyPress profile URL
+	 *
+	 * @since 2.6.0 bbPress (r3721)
+	 * @since 2.6.0 bbPress (r6803) Use private method
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_replies_created_url( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id, bbp_get_reply_archive_slug() );
+	}
+
+	/**
+	 * Override bbPress favorites URL with BuddyPress profile URL
+	 *
+	 * @since 2.1.0 bbPress (r3721)
+	 * @since 2.6.0 bbPress (r6803) Use private method
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_favorites_permalink( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id, bbp_get_user_favorites_slug() );
+	}
+
+	/**
+	 * Override bbPress subscriptions URL with BuddyPress profile URL
+	 *
+	 * @since 2.1.0 bbPress (r3721)
+	 * @since 2.6.0 bbPress (r6803) Use private method
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_subscriptions_permalink( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id, bbp_get_user_subscriptions_slug() );
+	}
+
+	/**
+	 * Override bbPress engagements URL with BuddyPress profile URL
+	 *
+	 * @since 2.6.0 bbPress (r6320)
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	public function get_engagements_permalink( $user_id = 0 ) {
+		return $this->get_profile_url( $user_id, bbp_get_user_engagements_slug() );
+	}
+
+	/**
+	 * Set favorites and subscriptions query variables if viewing member profile
+	 * pages.
+	 *
+	 * @since 2.3.0 bbPress (r4615)
+	 * @since 2.6.0 bbPress (r6320) Support all profile sections
+	 *
+	 * @global WP_Query $wp_query
+	 * @return If not viewing your own profile
+	 */
+	public function set_member_forum_query_vars() {
+
+		// Special handling for forum component
+		if ( ! bp_is_my_profile() ) {
+			return;
+		}
+
+		// Get the main query object
+		$wp_query = bbp_get_wp_query();
+
+		// 'topics' action
+		if ( bp_is_current_action( bbp_get_topic_archive_slug() ) ) {
+			$wp_query->bbp_is_single_user_topics = true;
+
+		// 'replies' action
+		} elseif ( bp_is_current_action( bbp_get_reply_archive_slug() ) ) {
+			$wp_query->bbp_is_single_user_replies = true;
+
+		// 'favorites' action
+		} elseif ( bbp_is_favorites_active() && bp_is_current_action( bbp_get_user_favorites_slug() ) ) {
+			$wp_query->bbp_is_single_user_favs = true;
+
+		// 'subscriptions' action
+		} elseif ( bbp_is_subscriptions_active() && bp_is_current_action( bbp_get_user_subscriptions_slug() ) ) {
+			$wp_query->bbp_is_single_user_subs = true;
+
+		// 'engagements' action
+		} elseif ( bbp_is_engagements_active() && bp_is_current_action( bbp_get_user_engagements_slug() ) ) {
+			$wp_query->bbp_is_single_user_engagements = true;
+		}
+	}
+
+	/** Private Methods *******************************************************/
+
+	/**
+	 * Private method used to concatenate user IDs and slugs into URLs
+	 *
+	 * @since 2.6.0 bbPress (r6803)
+	 *
+	 * @param int    $user_id
+	 * @param string $slug
+	 *
+	 * @return string
+	 */
+	private function get_profile_url( $user_id = 0, $slug = '' ) {
+
+		// Do not filter if not on BuddyPress root blog
+		if ( empty( $user_id ) || ! bp_is_root_blog() ) {
+			return false;
+		}
+
+		// Setup profile URL
+		$url = array( bp_core_get_user_domain( $user_id ) );
+
+		// Maybe push slug to end of URL array
+		if ( ! empty( $slug ) ) {
+			array_push( $url, bbpress()->extend->buddypress->slug );
+			array_push( $url, $slug );
+		}
+
+		// Return
+		return implode( '', array_map( 'trailingslashit', $url ) );
+	}
+}
+endif;
Index: /tags/2.6.6/src/includes/extend/buddypress/notifications.php
===================================================================
--- /tags/2.6.6/src/includes/extend/buddypress/notifications.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/buddypress/notifications.php	(revision 7162)
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * bbPress BuddyPress Notifications
+ *
+ * @package bbPress
+ * @subpackage BuddyPress
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Hooks
+add_filter( 'bp_notifications_get_registered_components', 'bbp_filter_notifications_get_registered_components', 10 );
+add_filter( 'bp_notifications_get_notifications_for_user', 'bbp_format_buddypress_notifications', 10, 8 );
+add_action( 'bbp_new_reply', 'bbp_buddypress_add_notification', 10, 7 );
+add_action( 'bbp_get_request', 'bbp_buddypress_mark_notifications', 1 );
+
+/** BuddyPress Helpers ********************************************************/
+
+/**
+ * Filter registered notifications components, and add 'forums' to the queried
+ * 'component_name' array.
+ *
+ * @since 2.6.0 bbPress (r5232)
+ *
+ * @see BP_Notifications_Notification::get()
+ * @param array $component_names
+ * @return array
+ */
+function bbp_filter_notifications_get_registered_components( $component_names = array() ) {
+
+	// Force $component_names to be an array
+	if ( ! is_array( $component_names ) ) {
+		$component_names = array();
+	}
+
+	// Add 'forums' component to registered components array
+	array_push( $component_names, bbp_get_component_name() );
+
+	// Return component's with 'forums' appended
+	return $component_names;
+}
+
+/**
+ * Format the BuddyBar/Toolbar notifications
+ *
+ * @since 2.5.0 bbPress (r5155)
+ *
+ * @package bbPress
+ *
+ * @param string $content               Component action. Deprecated. Do not do checks against this! Use
+ *                                      the 6th parameter instead - $component_action_name.
+ * @param int    $item_id               Notification item ID.
+ * @param int    $secondary_item_id     Notification secondary item ID.
+ * @param int    $action_item_count     Number of notifications with the same action.
+ * @param string $format                Format of return. Either 'string' or 'object'.
+ * @param string $component_action_name Canonical notification action.
+ * @param string $component_name        Notification component ID.
+ * @param int    $id                    Notification ID.
+ */
+function bbp_format_buddypress_notifications( $content, $item_id, $secondary_item_id, $action_item_count, $format, $component_action_name, $component_name, $id ) {
+
+	// Bail if not the notification action we are looking for
+	if ( 'bbp_new_reply' !== $component_action_name ) {
+		return $content;
+	}
+
+	// New reply notifications
+	$topic_id    = bbp_get_reply_topic_id( $item_id );
+	$topic_title = bbp_get_topic_title( $topic_id );
+	$topic_link  = wp_nonce_url(
+		add_query_arg(
+			array(
+				'action'   => 'bbp_mark_read',
+				'topic_id' => $topic_id
+			),
+			bbp_get_reply_url( $item_id )
+		),
+		'bbp_mark_topic_' . $topic_id
+	);
+
+	// Cast to int
+	$action_item_count = (int) $action_item_count;
+
+	// Multiple
+	if ( $action_item_count > 1 ) {
+		$filter = 'bbp_multiple_new_subscription_notification';
+		$text   = sprintf( esc_html__( 'You have %d new replies', 'bbpress' ), $action_item_count );
+
+	// Single
+	} else {
+		$filter = 'bbp_single_new_subscription_notification';
+		$text   = ! empty( $secondary_item_id )
+			? sprintf( esc_html__( 'You have %1$d new reply to %2$s from %3$s', 'bbpress' ), $action_item_count, $topic_title, bp_core_get_user_displayname( $secondary_item_id ) )
+			: sprintf( esc_html__( 'You have %1$d new reply to %2$s',           'bbpress' ), $action_item_count, $topic_title );
+	}
+
+	// WordPress Toolbar
+	if ( 'string' === $format ) {
+		$return = apply_filters( $filter, '<a href="' . esc_url( $topic_link ) . '" title="' . esc_attr__( 'Topic Replies', 'bbpress' ) . '">' . esc_html( $text ) . '</a>', $action_item_count, $text, $topic_link );
+
+	// Deprecated BuddyBar
+	} else {
+		$return = apply_filters( $filter, array(
+			'text' => $text,
+			'link' => $topic_link
+		), $topic_link, $action_item_count, $text, $topic_title );
+	}
+
+	do_action( 'bbp_format_buddypress_notifications', $component_action_name, $item_id, $secondary_item_id, $action_item_count, $format, $component_action_name, $component_name, $id );
+
+	return $return;
+}
+
+/**
+ * Hooked into the new reply function, this notification action is responsible
+ * for notifying topic and hierarchical reply authors of topic replies.
+ *
+ * @since 2.5.0 bbPress (r5156)
+ *
+ * @param int $reply_id
+ * @param int $topic_id
+ * @param int $forum_id (not used)
+ * @param array $anonymous_data (not used)
+ * @param int $author_id
+ * @param bool $is_edit Used to bail if this gets hooked to an edit action
+ * @param int $reply_to
+ */
+function bbp_buddypress_add_notification( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $author_id = 0, $is_edit = false, $reply_to = 0 ) {
+
+	// Bail if somehow this is hooked to an edit action
+	if ( ! empty( $is_edit ) ) {
+		return;
+	}
+
+	// Get author information
+	$topic_author_id   = bbp_get_topic_author_id( $topic_id );
+	$secondary_item_id = $author_id;
+
+	// Hierarchical replies
+	if ( ! empty( $reply_to ) ) {
+		$reply_to_item_id = bbp_get_topic_author_id( $reply_to );
+	}
+
+	// Get some reply information
+	$args = array(
+		'user_id'          => $topic_author_id,
+		'item_id'          => $reply_id,
+		'component_name'   => bbp_get_component_name(),
+		'component_action' => 'bbp_new_reply',
+		'date_notified'    => get_post( $reply_id )->post_date,
+	);
+
+	// Notify the topic author if not the current reply author
+	if ( $author_id !== $topic_author_id ) {
+		$args['secondary_item_id'] = $secondary_item_id;
+
+		bp_notifications_add_notification( $args );
+	}
+
+	// Notify the immediate reply author if not the current reply author
+	if ( ! empty( $reply_to ) && ( $author_id !== $reply_to_item_id ) ) {
+		$args['user_id']           = $reply_to_item_id;
+		$args['secondary_item_id'] = $topic_author_id;
+
+		bp_notifications_add_notification( $args );
+	}
+}
+
+/**
+ * Mark notifications as read when reading a topic
+ *
+ * @since 2.5.0 bbPress (r5155)
+ *
+ * @return If not trying to mark a notification as read
+ */
+function bbp_buddypress_mark_notifications( $action = '' ) {
+
+	// Bail if no topic ID is passed
+	if ( empty( $_GET['topic_id'] ) ) {
+		return;
+	}
+
+	// Bail if action is not for this function
+	if ( 'bbp_mark_read' !== $action ) {
+		return;
+	}
+
+	// Get required data
+	$user_id  = bp_loggedin_user_id();
+	$topic_id = absint( $_GET['topic_id'] );
+
+	// By default, Redirect to this topic ID
+	$redirect_id = $topic_id;
+
+	// Check nonce
+	if ( ! bbp_verify_nonce_request( 'bbp_mark_topic_' . $topic_id ) ) {
+		bbp_add_error( 'bbp_notification_topic_id', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+
+	// Check current user's ability to edit the user
+	} elseif ( ! current_user_can( 'edit_user', $user_id ) ) {
+		bbp_add_error( 'bbp_notification_permission', __( '<strong>Error</strong>: You do not have permission to mark notifications for that user.', 'bbpress' ) );
+	}
+
+	// Bail if we have errors
+	if ( ! bbp_has_errors() ) {
+
+		// Get these once
+		$post_type = bbp_get_reply_post_type();
+		$component = bbp_get_component_name();
+
+		// Attempt to clear notifications for this topic
+		$marked    = bp_notifications_mark_notifications_by_item_id( $user_id, $topic_id, $component, 'bbp_new_reply' );
+
+		// Get all reply IDs for the topic
+		$replies   = bbp_get_all_child_ids( $topic_id, $post_type );
+
+		// If topic has replies
+		if ( ! empty( $replies ) ) {
+
+			// Loop through each reply and attempt to mark it
+			foreach ( $replies as $reply_id ) {
+
+				// Attempt to mark notification for this reply ID
+				$marked = bp_notifications_mark_notifications_by_item_id( $user_id, $reply_id, $component, 'bbp_new_reply' );
+
+				// If marked, redirect to this reply ID
+				if ( ! empty( $marked ) ) {
+					$redirect_id = $reply_id;
+				}
+			}
+		}
+
+		// Do additional subscriptions actions
+		do_action( 'bbp_notifications_handler', $marked, $user_id, $topic_id, $action );
+	}
+
+	// Redirect to the topic
+	$redirect = bbp_get_reply_url( $redirect_id );
+
+	// Redirect
+	bbp_redirect( $redirect );
+}
Index: /tags/2.6.6/src/includes/extend/index.php
===================================================================
--- /tags/2.6.6/src/includes/extend/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/extend/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/forums/capabilities.php
===================================================================
--- /tags/2.6.6/src/includes/forums/capabilities.php	(revision 7162)
+++ /tags/2.6.6/src/includes/forums/capabilities.php	(revision 7162)
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * bbPress Forum Capabilites
+ *
+ * Used to map forum capabilities to WordPress's existing capabilities.
+ *
+ * @package bbPress
+ * @subpackage Capabilities
+ */
+
+/**
+ * Return forum capabilities
+ *
+ * @since 2.0.0 bbPress (r2593)
+ *
+ * @return array Forum capabilities
+ */
+function bbp_get_forum_caps() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_caps', array(
+		'edit_posts'          => 'edit_forums',
+		'edit_others_posts'   => 'edit_others_forums',
+		'publish_posts'       => 'publish_forums',
+		'read_private_posts'  => 'read_private_forums',
+		'read_hidden_posts'   => 'read_hidden_forums',
+		'delete_posts'        => 'delete_forums',
+		'delete_others_posts' => 'delete_others_forums'
+	) );
+}
+
+/**
+ * Maps forum capabilities
+ *
+ * @since 2.2.0 bbPress (r4242)
+ *
+ * @param array $caps Capabilities for meta capability
+ * @param string $cap Capability name
+ * @param int $user_id User id
+ * @param array $args Arguments
+ * @return array Actual capabilities for meta capability
+ */
+function bbp_map_forum_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+
+		/** Reading ***********************************************************/
+
+		case 'read_private_forums' :
+		case 'read_hidden_forums'  :
+
+			// Moderators can always read private/hidden forums
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		case 'read_forum' :
+
+			// User cannot spectate
+			if ( ! user_can( $user_id, 'spectate' ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Do some post ID based logic
+			} else {
+
+				// Bail if no post ID
+				if ( empty( $args[0] ) ) {
+					break;
+				}
+
+				// Get the post.
+				$_post = get_post( $args[0] );
+				if ( ! empty( $_post ) ) {
+
+					// Get caps for post type object
+					$post_type = get_post_type_object( $_post->post_type );
+
+					// Post is public
+					if ( bbp_get_public_status_id() === $_post->post_status ) {
+						$caps = array( 'spectate' );
+
+					// User is author so allow read
+					} elseif ( (int) $user_id === (int) $_post->post_author ) {
+						$caps = array( 'spectate' );
+
+					// Moderators can always read forum content
+					} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+						$caps = array( 'spectate' );
+
+					// Private
+					} elseif ( bbp_get_hidden_status_id() === $_post->post_status ) {
+						$caps = array( $post_type->cap->read_hidden_posts );
+
+					// Hidden
+					} elseif ( bbp_get_private_status_id() === $_post->post_status ) {
+						$caps = array( $post_type->cap->read_private_posts );
+
+					// Unknown, so map to private
+					} else {
+						$caps = array( $post_type->cap->read_private_posts );
+					}
+				}
+			}
+
+			break;
+
+		/** Publishing ********************************************************/
+
+		case 'publish_forums'  :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		/** Editing ***********************************************************/
+
+		// Used primarily in wp-admin
+		case 'edit_forums'         :
+		case 'edit_others_forums'  :
+
+			// Moderators can always edit
+			if ( bbp_is_user_keymaster( $user_id ) ) {
+				$caps = array( 'spectate' );
+
+			// Otherwise, block
+			} else {
+				$caps = array( 'do_not_allow' );
+			}
+
+			break;
+
+		// Used everywhere
+		case 'edit_forum' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post.
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always read forum content
+				} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+					$caps = array( 'spectate' );
+
+				// User is author so allow edit if not in admin
+				} elseif ( ! is_admin() && ( (int) $user_id === (int) $_post->post_author ) ) {
+					$caps = array( $post_type->cap->edit_posts );
+
+				// Unknown, so map to edit_others_posts
+				} else {
+					$caps = array( $post_type->cap->edit_others_posts );
+				}
+			}
+
+			break;
+
+		/** Deleting **********************************************************/
+
+		// Allow forum authors to delete forums (for BuddyPress groups, etc)
+		case 'delete_forum' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post.
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// User is author so allow to delete
+				} elseif ( (int) $user_id === (int) $_post->post_author ) {
+					$caps = array( $post_type->cap->delete_posts );
+
+				// Unknown so map to delete_others_posts
+				} else {
+					$caps = array( $post_type->cap->delete_others_posts );
+				}
+			}
+
+			break;
+
+		/** Admin *************************************************************/
+
+		// Forum admin area.
+		case 'bbp_forums_admin' :
+			$caps = array( 'edit_forums' );
+			break;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_forum_meta_caps', $caps, $cap, $user_id, $args );
+}
+
+/**
+ * Can a user moderate a forum?
+ *
+ * @since 2.6.0 bbPress (r5834)
+ *
+ * @param int $user_id User id.
+ * @param int $forum_id Forum id.
+ *
+ * @return bool Return true if user is moderator of forum
+ */
+function bbp_is_user_forum_moderator( $user_id = 0, $forum_id = 0 ) {
+	$user_id  = bbp_get_user_id( $user_id, false, empty( $user_id ) );
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$retval   = user_can( $user_id, 'moderate', $forum_id );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_forum_moderator', $retval, $user_id, $forum_id );
+}
+
+/**
+ * Filter an array of forum IDs that are being excluded, and remove any forum
+ * IDs a user explicitly has access to.
+ *
+ * This typically means private or hidden forums the user has moderation rights
+ * to, but it can be filtered to mean just about anything.
+ *
+ * This function filters the return values of the following functions:
+ * - `bbp_get_private_forum_ids()`
+ * - `bbp_get_hidden_forum_ids()`
+ *
+ * @since 2.6.0 bbPress (r6426)
+ *
+ * @param array $forum_ids Forum IDs to check if the user ID is a moderator of
+ * @param int   $user_id   User ID to check if is a moderator of forums
+ *
+ * @return array
+ */
+function bbp_allow_forums_of_user( $forum_ids = array(), $user_id = 0 ) {
+
+	// Store the original forum IDs
+	$original_forum_ids = $forum_ids;
+
+	// Per-forum Moderators
+	if ( bbp_allow_forum_mods() ) {
+
+		// Loop through forum IDs
+		foreach ( $forum_ids as $key => $forum_id ) {
+
+			// Unset forum ID if user is a moderator
+			if ( bbp_is_user_forum_moderator( $user_id, $forum_id ) ) {
+				unset( $forum_ids[ $key ] );
+			}
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_allow_forums_of_user', $forum_ids, $user_id, $original_forum_ids );
+}
Index: /tags/2.6.6/src/includes/forums/functions.php
===================================================================
--- /tags/2.6.6/src/includes/forums/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/forums/functions.php	(revision 7162)
@@ -0,0 +1,2791 @@
+<?php
+
+/**
+ * bbPress Forum Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Insert ********************************************************************/
+
+/**
+ * A wrapper for wp_insert_post() that also includes the necessary meta values
+ * for the forum to function properly.
+ *
+ * @since 2.0.0 bbPress (r3349)
+ *
+ * @param array $forum_data Forum post data
+ * @param arrap $forum_meta Forum meta data
+ */
+function bbp_insert_forum( $forum_data = array(), $forum_meta = array() ) {
+
+	// Forum
+	$forum_data = bbp_parse_args( $forum_data, array(
+		'post_parent'    => 0, // forum ID
+		'post_status'    => bbp_get_public_status_id(),
+		'post_type'      => bbp_get_forum_post_type(),
+		'post_author'    => bbp_get_current_user_id(),
+		'post_password'  => '',
+		'post_content'   => '',
+		'post_title'     => '',
+		'menu_order'     => 0,
+		'comment_status' => 'closed'
+	), 'insert_forum' );
+
+	// Insert forum
+	$forum_id = wp_insert_post( $forum_data, false );
+
+	// Bail if no forum was added
+	if ( empty( $forum_id ) ) {
+		return false;
+	}
+
+	// Forum meta
+	$forum_meta = bbp_parse_args( $forum_meta, array(
+		'forum_type'           => 'forum',
+		'status'               => 'open',
+		'reply_count'          => 0,
+		'topic_count'          => 0,
+		'topic_count_hidden'   => 0,
+		'total_reply_count'    => 0,
+		'total_topic_count'    => 0,
+		'last_topic_id'        => 0,
+		'last_reply_id'        => 0,
+		'last_active_id'       => 0,
+		'last_active_time'     => 0,
+		'forum_subforum_count' => 0,
+	), 'insert_forum_meta' );
+
+	// Insert forum meta
+	foreach ( $forum_meta as $meta_key => $meta_value ) {
+
+		// Prefix if not prefixed
+		if ( '_bbp_' !== substr( $meta_key, 0, 5 ) ) {
+			$meta_key = '_bbp_' . $meta_key;
+		}
+
+		// Update the meta
+		update_post_meta( $forum_id, $meta_key, $meta_value );
+	}
+
+	// Update the forum and hierarchy
+	bbp_update_forum( array(
+		'forum_id'    => $forum_id,
+		'post_parent' => $forum_data['post_parent']
+	) );
+
+	// Maybe make private
+	if ( bbp_is_forum_private( $forum_id, false ) ) {
+		bbp_privatize_forum( $forum_id );
+
+	// Maybe make hidden
+	} elseif ( bbp_is_forum_hidden( $forum_id, false ) ) {
+		bbp_hide_forum( $forum_id );
+
+	// Publicize
+	} else {
+		bbp_publicize_forum( $forum_id );
+	}
+
+	/**
+	 * Fires after forum has been inserted via `bbp_insert_forum`.
+	 *
+	 * @since 2.6.0 bbPress (r6036)
+	 *
+	 * @param int $forum_id The forum id.
+	 */
+	do_action( 'bbp_insert_forum', (int) $forum_id );
+
+	// Bump the last changed cache
+	wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
+
+	// Return forum_id
+	return $forum_id;
+}
+
+/** Post Form Handlers ********************************************************/
+
+/**
+ * Handles the front end forum submission
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_new_forum_handler( $action = '' ) {
+
+	// Bail if action is not bbp-new-forum
+	if ( 'bbp-new-forum' !== $action ) {
+		return;
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-new-forum' ) ) {
+		bbp_add_error( 'bbp_new_forum_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Define local variable(s)
+	$view_all = false;
+	$forum_parent_id = $forum_author = 0;
+	$forum_title = $forum_content = '';
+	$anonymous_data = array();
+
+	/** Forum Author **********************************************************/
+
+	// User cannot create forums
+	if ( ! current_user_can( 'publish_forums' ) ) {
+		bbp_add_error( 'bbp_forum_permission', __( '<strong>Error</strong>: You do not have permission to create new forums.', 'bbpress' ) );
+		return;
+	}
+
+	// Forum author is current user
+	$forum_author = bbp_get_current_user_id();
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_forum'] ) && wp_create_nonce( 'bbp-unfiltered-html-forum_new' ) === $_POST['_bbp_unfiltered_html_forum'] ) {
+		remove_filter( 'bbp_new_forum_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_new_forum_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_new_forum_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Forum Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_title'] ) ) {
+		$forum_title = sanitize_text_field( $_POST['bbp_forum_title'] );
+	}
+
+	// Filter and sanitize
+	$forum_title = apply_filters( 'bbp_new_forum_pre_title', $forum_title );
+
+	// No forum title
+	if ( empty( $forum_title ) ) {
+		bbp_add_error( 'bbp_forum_title', __( '<strong>Error</strong>: Your forum needs a title.', 'bbpress' ) );
+	}
+
+	// Title too long
+	if ( bbp_is_title_too_long( $forum_title ) ) {
+		bbp_add_error( 'bbp_forum_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Forum Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_content'] ) ) {
+		$forum_content = $_POST['bbp_forum_content'];
+	}
+
+	// Filter and sanitize
+	$forum_content = apply_filters( 'bbp_new_forum_pre_content', $forum_content );
+
+	// No forum content
+	if ( empty( $forum_content ) ) {
+		bbp_add_error( 'bbp_forum_content', __( '<strong>Error</strong>: Your forum description cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Forum Parent **********************************************************/
+
+	// Forum parent was passed (the norm)
+	if ( ! empty( $_POST['bbp_forum_parent_id'] ) ) {
+		$forum_parent_id = bbp_get_forum_id( $_POST['bbp_forum_parent_id'] );
+	}
+
+	// Filter and sanitize
+	$forum_parent_id = apply_filters( 'bbp_new_forum_pre_parent_id', $forum_parent_id );
+
+	// No forum parent was passed (should never happen)
+	if ( empty( $forum_parent_id ) ) {
+		bbp_add_error( 'bbp_new_forum_missing_parent', __( '<strong>Error</strong>: Your forum must have a parent.', 'bbpress' ) );
+
+	// Forum exists
+	} elseif ( ! empty( $forum_parent_id ) ) {
+
+		// Forum is a category
+		if ( bbp_is_forum_category( $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_new_forum_forum_category', __( '<strong>Error</strong>: This forum is a category. No forums can be created in this forum.', 'bbpress' ) );
+		}
+
+		// Forum is closed and user cannot access
+		if ( bbp_is_forum_closed( $forum_parent_id ) && ! current_user_can( 'edit_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_new_forum_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new forums.', 'bbpress' ) );
+		}
+
+		// Forum is private and user cannot access
+		if ( bbp_is_forum_private( $forum_parent_id ) && ! current_user_can( 'read_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_new_forum_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new forums in it.', 'bbpress' ) );
+		}
+
+		// Forum is hidden and user cannot access
+		if ( bbp_is_forum_hidden( $forum_parent_id ) && ! current_user_can( 'read_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_new_forum_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new forums in it.', 'bbpress' ) );
+		}
+	}
+
+	/** Forum Flooding ********************************************************/
+
+	if ( ! bbp_check_for_flood( $anonymous_data, $forum_author ) ) {
+		bbp_add_error( 'bbp_forum_flood', __( '<strong>Error</strong>: Slow down; you move too fast.', 'bbpress' ) );
+	}
+
+	/** Forum Duplicate *******************************************************/
+
+	if ( ! bbp_check_for_duplicate( array( 'post_type' => bbp_get_forum_post_type(), 'post_author' => $forum_author, 'post_content' => $forum_content, 'anonymous_data' => $anonymous_data ) ) ) {
+		bbp_add_error( 'bbp_forum_duplicate', __( '<strong>Error</strong>: This forum already exists.', 'bbpress' ) );
+	}
+
+	/** Forum Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, $forum_author, $forum_title, $forum_content, true ) ) {
+		bbp_add_error( 'bbp_forum_moderation', __( '<strong>Error</strong>: Your forum cannot be created at this time.', 'bbpress' ) );
+	}
+
+	/** Forum Moderation ******************************************************/
+
+	$post_status = bbp_get_public_status_id();
+	if ( ! bbp_check_for_moderation( $anonymous_data, $forum_author, $forum_title, $forum_content ) ) {
+		$post_status = bbp_get_pending_status_id();
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_new_forum_pre_extras', $forum_parent_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $forum_data as an array
+	// Just in time manipulation of forum data before being created
+	$forum_data = apply_filters( 'bbp_new_forum_pre_insert', array(
+		'post_author'    => $forum_author,
+		'post_title'     => $forum_title,
+		'post_content'   => $forum_content,
+		'post_parent'    => $forum_parent_id,
+		'post_status'    => $post_status,
+		'post_type'      => bbp_get_forum_post_type(),
+		'comment_status' => 'closed'
+	) );
+
+	// Insert forum
+	$forum_id = wp_insert_post( $forum_data, true );
+
+	/** No Errors *************************************************************/
+
+	if ( ! empty( $forum_id ) && ! is_wp_error( $forum_id ) ) {
+
+		/** Trash Check *******************************************************/
+
+		// If the forum is trash, or the forum_status is switched to
+		// trash, trash it properly
+		if ( ( get_post_field( 'post_status', $forum_id ) === bbp_get_trash_status_id() ) || ( $forum_data['post_status'] === bbp_get_trash_status_id() ) ) {
+
+			// Trash the reply
+			wp_trash_post( $forum_id );
+
+			// Force view=all
+			$view_all = true;
+		}
+
+		/** Spam Check ********************************************************/
+
+		// If reply or forum are spam, officially spam this reply
+		if ( $forum_data['post_status'] === bbp_get_spam_status_id() ) {
+			add_post_meta( $forum_id, '_bbp_spam_meta_status', bbp_get_public_status_id() );
+
+			// Force view=all
+			$view_all = true;
+		}
+
+		/** Update counts, etc... *********************************************/
+
+		do_action( 'bbp_new_forum', array(
+			'forum_id'           => $forum_id,
+			'post_parent'        => $forum_parent_id,
+			'forum_author'       => $forum_author,
+			'last_topic_id'      => 0,
+			'last_reply_id'      => 0,
+			'last_active_id'     => 0,
+			'last_active_time'   => 0,
+			'last_active_status' => bbp_get_public_status_id()
+		) );
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_new_forum_post_extras', $forum_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to  = bbp_get_redirect_to();
+
+		// Get the forum URL
+		$redirect_url = bbp_get_forum_permalink( $forum_id, $redirect_to );
+
+		// Add view all?
+		if ( bbp_get_view_all() || ! empty( $view_all ) ) {
+
+			// User can moderate, so redirect to forum with view all set
+			if ( current_user_can( 'moderate', $forum_id ) ) {
+				$redirect_url = bbp_add_view_all( $redirect_url );
+
+			// User cannot moderate, so redirect to forum
+			} else {
+				$redirect_url = bbp_get_forum_permalink( $forum_id );
+			}
+		}
+
+		// Allow to be filtered
+		$redirect_url = apply_filters( 'bbp_new_forum_redirect_to', $redirect_url, $redirect_to );
+
+		/** Successful Save ***************************************************/
+
+		// Redirect back to new forum
+		bbp_redirect( $redirect_url );
+
+	/** Errors ****************************************************************/
+
+	// WP_Error
+	} elseif ( is_wp_error( $forum_id ) ) {
+		bbp_add_error( 'bbp_forum_error', sprintf( __( '<strong>Error</strong>: The following problem(s) occurred: %s', 'bbpress' ), $forum_id->get_error_message() ) );
+
+	// Generic error
+	} else {
+		bbp_add_error( 'bbp_forum_error', __( '<strong>Error</strong>: The forum was not created.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handles the front end edit forum submission
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_edit_forum_handler( $action = '' ) {
+
+	// Bail if action is not bbp-edit-forum
+	if ( 'bbp-edit-forum' !== $action ) {
+		return;
+	}
+
+	// Define local variable(s)
+	$anonymous_data = array();
+	$forum = $forum_id = $forum_parent_id = 0;
+	$forum_title = $forum_content = $forum_edit_reason = '';
+
+	/** Forum *****************************************************************/
+
+	// Forum id was not passed
+	if ( empty( $_POST['bbp_forum_id'] ) ) {
+		bbp_add_error( 'bbp_edit_forum_id', __( '<strong>Error</strong>: Forum ID not found.', 'bbpress' ) );
+		return;
+
+	// Forum id was passed
+	} elseif ( is_numeric( $_POST['bbp_forum_id'] ) ) {
+		$forum_id = (int) $_POST['bbp_forum_id'];
+		$forum    = bbp_get_forum( $forum_id );
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-edit-forum_' . $forum_id ) ) {
+		bbp_add_error( 'bbp_edit_forum_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+
+	// Forum does not exist
+	} elseif ( empty( $forum ) ) {
+		bbp_add_error( 'bbp_edit_forum_not_found', __( '<strong>Error</strong>: The forum you want to edit was not found.', 'bbpress' ) );
+		return;
+
+	// User cannot edit this forum
+	} elseif ( ! current_user_can( 'edit_forum', $forum_id ) ) {
+		bbp_add_error( 'bbp_edit_forum_permission', __( '<strong>Error</strong>: You do not have permission to edit that forum.', 'bbpress' ) );
+		return;
+	}
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_forum'] ) && ( wp_create_nonce( 'bbp-unfiltered-html-forum_' . $forum_id ) === $_POST['_bbp_unfiltered_html_forum'] ) ) {
+		remove_filter( 'bbp_edit_forum_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_edit_forum_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_edit_forum_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Forum Parent ***********************************************************/
+
+	// Forum parent id was passed
+	if ( ! empty( $_POST['bbp_forum_parent_id'] ) ) {
+		$forum_parent_id = bbp_get_forum_id( $_POST['bbp_forum_parent_id'] );
+	}
+
+	// Current forum this forum is in
+	$current_parent_forum_id = bbp_get_forum_parent_id( $forum_id );
+
+	// Forum exists
+	if ( ! empty( $forum_parent_id ) && ( $forum_parent_id !== $current_parent_forum_id ) ) {
+
+		// Forum is closed and user cannot access
+		if ( bbp_is_forum_closed( $forum_parent_id ) && ! current_user_can( 'edit_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_edit_forum_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new forums.', 'bbpress' ) );
+		}
+
+		// Forum is private and user cannot access
+		if ( bbp_is_forum_private( $forum_parent_id ) && ! current_user_can( 'read_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_edit_forum_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new forums in it.', 'bbpress' ) );
+		}
+
+		// Forum is hidden and user cannot access
+		if ( bbp_is_forum_hidden( $forum_parent_id ) && ! current_user_can( 'read_forum', $forum_parent_id ) ) {
+			bbp_add_error( 'bbp_edit_forum_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new forums in it.', 'bbpress' ) );
+		}
+	}
+
+	/** Forum Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_title'] ) ) {
+		$forum_title = sanitize_text_field( $_POST['bbp_forum_title'] );
+	}
+
+	// Filter and sanitize
+	$forum_title = apply_filters( 'bbp_edit_forum_pre_title', $forum_title, $forum_id );
+
+	// No forum title
+	if ( empty( $forum_title ) ) {
+		bbp_add_error( 'bbp_edit_forum_title', __( '<strong>Error</strong>: Your forum needs a title.', 'bbpress' ) );
+	}
+
+	// Title too long
+	if ( bbp_is_title_too_long( $forum_title ) ) {
+		bbp_add_error( 'bbp_forum_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Forum Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_content'] ) ) {
+		$forum_content = $_POST['bbp_forum_content'];
+	}
+
+	// Filter and sanitize
+	$forum_content = apply_filters( 'bbp_edit_forum_pre_content', $forum_content, $forum_id );
+
+	// No forum content
+	if ( empty( $forum_content ) ) {
+		bbp_add_error( 'bbp_edit_forum_content', __( '<strong>Error</strong>: Your forum description cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Forum Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, bbp_get_forum_author_id( $forum_id ), $forum_title, $forum_content, true ) ) {
+		bbp_add_error( 'bbp_forum_moderation', __( '<strong>Error</strong>: Your forum cannot be edited at this time.', 'bbpress' ) );
+	}
+
+	/** Forum Moderation ******************************************************/
+
+	$post_status = bbp_get_public_status_id();
+	if ( ! bbp_check_for_moderation( $anonymous_data, bbp_get_forum_author_id( $forum_id ), $forum_title, $forum_content ) ) {
+		$post_status = bbp_get_pending_status_id();
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_edit_forum_pre_extras', $forum_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $forum_data as an array
+	// Just in time manipulation of forum data before being edited
+	$forum_data = apply_filters( 'bbp_edit_forum_pre_insert', array(
+		'ID'           => $forum_id,
+		'post_title'   => $forum_title,
+		'post_content' => $forum_content,
+		'post_status'  => $post_status,
+		'post_parent'  => $forum_parent_id
+	) );
+
+	// Insert forum
+	$forum_id = wp_update_post( $forum_data );
+
+	/** No Errors *************************************************************/
+
+	if ( ! empty( $forum_id ) && ! is_wp_error( $forum_id ) ) {
+
+		// Update counts, etc...
+		do_action( 'bbp_edit_forum', array(
+			'forum_id'           => $forum_id,
+			'post_parent'        => $forum_parent_id,
+			'forum_author'       => $forum->post_author,
+			'last_topic_id'      => 0,
+			'last_reply_id'      => 0,
+			'last_active_id'     => 0,
+			'last_active_time'   => 0,
+			'last_active_status' => bbp_get_public_status_id()
+		) );
+
+		/** Revisions *********************************************************/
+
+		// Update locks
+		update_post_meta( $forum_id, '_edit_last', bbp_get_current_user_id() );
+		delete_post_meta( $forum_id, '_edit_lock' );
+
+		/**
+		 * @todo omitted for now
+		// Revision Reason
+		if ( ! empty( $_POST['bbp_forum_edit_reason'] ) )
+			$forum_edit_reason = sanitize_text_field( $_POST['bbp_forum_edit_reason'] );
+
+		// Update revision log
+		if ( ! empty( $_POST['bbp_log_forum_edit'] ) && ( "1" === $_POST['bbp_log_forum_edit'] ) && ( $revision_id = wp_save_post_revision( $forum_id ) ) ) {
+			bbp_update_forum_revision_log( array(
+				'forum_id'    => $forum_id,
+				'revision_id' => $revision_id,
+				'author_id'   => bbp_get_current_user_id(),
+				'reason'      => $forum_edit_reason
+			) );
+		}
+
+		// If the new forum parent id is not equal to the old forum parent
+		// id, run the bbp_move_forum action and pass the forum's parent id
+		// as the first argument and new forum parent id as the second.
+		// @todo implement
+		if ( $forum_id !== $forum->post_parent ) {
+			bbp_move_forum_handler( $forum_parent_id, $forum->post_parent, $forum_id );
+		}
+
+		*/
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_edit_forum_post_extras', $forum_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to = bbp_get_redirect_to();
+
+		// View all?
+		$view_all = bbp_get_view_all();
+
+		// Get the forum URL
+		$forum_url = bbp_get_forum_permalink( $forum_id, $redirect_to );
+
+		// Add view all?
+		if ( ! empty( $view_all ) ) {
+			$forum_url = bbp_add_view_all( $forum_url );
+		}
+
+		// Allow to be filtered
+		$forum_url = apply_filters( 'bbp_edit_forum_redirect_to', $forum_url, $view_all, $redirect_to );
+
+		/** Successful Edit ***************************************************/
+
+		// Redirect back to new forum
+		bbp_redirect( $forum_url );
+
+	/** Errors ****************************************************************/
+
+	} else {
+		$append_error = ( is_wp_error( $forum_id ) && $forum_id->get_error_message() ) ? $forum_id->get_error_message() . ' ' : '';
+		bbp_add_error( 'bbp_forum_error', __( '<strong>Error</strong>: The following problem(s) have been found with your forum:' . $append_error . 'Please try again.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handle the saving of core forum metadata (Status, Visibility, and Type)
+ *
+ * @since 2.1.0 bbPress (r3678)
+ *
+ * @param int $forum_id
+ * @return If forum ID is empty
+ */
+function bbp_save_forum_extras( $forum_id = 0 ) {
+
+	// Validate the forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Bail if forum ID is empty
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return;
+	}
+
+	/** Forum Status **********************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_status'] ) && in_array( $_POST['bbp_forum_status'], array( 'open', 'closed' ), true ) ) {
+		if ( 'closed' === $_POST['bbp_forum_status'] && ! bbp_is_forum_closed( $forum_id, false ) ) {
+			bbp_close_forum( $forum_id );
+		} elseif ( 'open' === $_POST['bbp_forum_status'] && bbp_is_forum_open( $forum_id, false ) ) {
+			bbp_open_forum( $forum_id );
+		} elseif ( 'open' === $_POST['bbp_forum_status'] && bbp_is_forum_closed( $forum_id, false ) ) {
+			bbp_open_forum( $forum_id );
+		}
+	}
+
+	/** Forum Type ************************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_type'] ) && in_array( $_POST['bbp_forum_type'], array( 'forum', 'category' ), true ) ) {
+		if ( 'category' === $_POST['bbp_forum_type'] && ! bbp_is_forum_category( $forum_id ) ) {
+			bbp_categorize_forum( $forum_id );
+		} elseif ( 'forum' === $_POST['bbp_forum_type'] && ! bbp_is_forum_category( $forum_id ) ) {
+			bbp_normalize_forum( $forum_id );
+		} elseif ( 'forum' === $_POST['bbp_forum_type'] && bbp_is_forum_category( $forum_id ) ) {
+			bbp_normalize_forum( $forum_id );
+		}
+	}
+
+	/** Forum Visibility ******************************************************/
+
+	if ( ! empty( $_POST['bbp_forum_visibility'] ) && in_array( $_POST['bbp_forum_visibility'], array_keys( bbp_get_forum_visibilities() ), true ) ) {
+
+		// Get forums current visibility
+		$old_visibility = bbp_get_forum_visibility( $forum_id );
+
+		// Sanitize the new visibility
+		$new_visibility = sanitize_key( $_POST['bbp_forum_visibility'] );
+
+		// What is the new forum visibility setting?
+		switch ( $new_visibility ) {
+
+			// Hidden
+			case bbp_get_hidden_status_id()  :
+				bbp_hide_forum( $forum_id, $old_visibility );
+				break;
+
+			// Private
+			case bbp_get_private_status_id() :
+				bbp_privatize_forum( $forum_id, $old_visibility );
+				break;
+
+			// Publish (default)
+			case bbp_get_public_status_id()  :
+			default :
+				bbp_publicize_forum( $forum_id, $old_visibility );
+				break;
+		}
+
+		/**
+		 * Allow custom forum visibility save actions
+		 *
+		 * @since 2.6.0 bbPress (r5855)
+		 *
+		 * @param int    $forum_id       The forum ID
+		 * @param string $old_visibility The current forum visibility
+		 * @param string $new_visibility The new forum visibility
+		 */
+		do_action( 'bbp_update_forum_visibility', $forum_id, $old_visibility, $new_visibility );
+	}
+
+	/** Forum Moderators ******************************************************/
+
+	// Either replace terms
+	if ( bbp_allow_forum_mods() ) {
+		if ( current_user_can( 'assign_moderators' ) && ! empty( $_POST['bbp_moderators'] ) ) {
+
+			// Escape tag input
+			$users    = sanitize_text_field( $_POST['bbp_moderators'] );
+			$user_ids = bbp_get_user_ids_from_nicenames( $users );
+
+			// Update forum moderators
+			if ( ! empty( $user_ids ) ) {
+
+				// Remove all moderators
+				bbp_remove_moderator( $forum_id, null );
+
+				// Add moderators
+				foreach ( $user_ids as $user_id ) {
+					bbp_add_moderator( $forum_id, $user_id );
+				}
+			}
+
+		// ...or remove them.
+		} elseif ( isset( $_POST['bbp_moderators'] ) ) {
+			bbp_remove_moderator( $forum_id, null );
+		}
+	}
+}
+
+/** Forum Open/Close **********************************************************/
+
+/**
+ * Closes a forum
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id forum id
+ * @return mixed False or {@link WP_Error} on failure, forum id on success
+ */
+function bbp_close_forum( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_close_forum',  $forum_id );
+
+	update_post_meta( $forum_id, '_bbp_status', 'closed' );
+
+	do_action( 'bbp_closed_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Opens a forum
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id forum id
+ * @return mixed False or {@link WP_Error} on failure, forum id on success
+ */
+function bbp_open_forum( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_open_forum',   $forum_id );
+
+	update_post_meta( $forum_id, '_bbp_status', 'open' );
+
+	do_action( 'bbp_opened_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/** Forum Type ****************************************************************/
+
+/**
+ * Make the forum a category
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool False on failure, true on success
+ */
+function bbp_categorize_forum( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_categorize_forum',  $forum_id );
+
+	update_post_meta( $forum_id, '_bbp_forum_type', 'category' );
+
+	do_action( 'bbp_categorized_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Remove the category status from a forum
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool False on failure, true on success
+ */
+function bbp_normalize_forum( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_normalize_forum',  $forum_id );
+
+	update_post_meta( $forum_id, '_bbp_forum_type', 'forum' );
+
+	do_action( 'bbp_normalized_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/** Forum Visibility **********************************************************/
+
+/**
+ * Mark the forum as public
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool False on failure, true on success
+ */
+function bbp_publicize_forum( $forum_id = 0, $current_visibility = '' ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_publicize_forum',  $forum_id );
+
+	// Get private forums
+	$private = bbp_get_private_forum_ids();
+
+	// Find this forum in the array
+	if ( in_array( $forum_id, $private, true ) ) {
+
+		$offset = array_search( $forum_id, $private, true );
+
+		// Splice around it
+		array_splice( $private, $offset, 1 );
+
+		// Update private forums minus this one
+		update_option( '_bbp_private_forums', bbp_get_unique_array_values( $private ) );
+	}
+
+	// Get hidden forums
+	$hidden = bbp_get_hidden_forum_ids();
+
+	// Find this forum in the array
+	if ( in_array( $forum_id, $hidden, true ) ) {
+
+		$offset = array_search( $forum_id, $hidden, true );
+
+		// Splice around it
+		array_splice( $hidden, $offset, 1 );
+
+		// Update hidden forums minus this one
+		update_option( '_bbp_hidden_forums', bbp_get_unique_array_values( $hidden ) );
+	}
+
+	// Only run queries if visibility is changing
+	if ( bbp_get_public_status_id() !== $current_visibility ) {
+		$bbp_db = bbp_db();
+		$bbp_db->update( $bbp_db->posts, array( 'post_status' => bbp_get_public_status_id() ), array( 'ID' => $forum_id ) );
+		wp_transition_post_status( bbp_get_public_status_id(), $current_visibility, get_post( $forum_id ) );
+		clean_post_cache( $forum_id );
+	}
+
+	do_action( 'bbp_publicized_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Mark the forum as private
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool False on failure, true on success
+ */
+function bbp_privatize_forum( $forum_id = 0, $current_visibility = '' ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_privatize_forum',  $forum_id );
+
+	// Only run queries if visibility is changing
+	if ( bbp_get_private_status_id() !== $current_visibility ) {
+
+		// Get hidden forums
+		$hidden = bbp_get_hidden_forum_ids();
+
+		// Find this forum in the array
+		if ( in_array( $forum_id, $hidden, true ) ) {
+
+			$offset = array_search( $forum_id, $hidden, true );
+
+			// Splice around it
+			array_splice( $hidden, $offset, 1 );
+
+			// Update hidden forums minus this one
+			update_option( '_bbp_hidden_forums', bbp_get_unique_array_values( $hidden ) );
+		}
+
+		// Add to '_bbp_private_forums' site option
+		$private   = bbp_get_private_forum_ids();
+		$private[] = $forum_id;
+		update_option( '_bbp_private_forums', bbp_get_unique_array_values( $private ) );
+
+		// Update forums visibility setting
+		$bbp_db = bbp_db();
+		$bbp_db->update( $bbp_db->posts, array( 'post_status' => bbp_get_private_status_id() ), array( 'ID' => $forum_id ) );
+		wp_transition_post_status( bbp_get_private_status_id(), $current_visibility, get_post( $forum_id ) );
+		clean_post_cache( $forum_id );
+	}
+
+	do_action( 'bbp_privatized_forum', $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Mark the forum as hidden
+ *
+ * @since 2.0.0 bbPress (r2996)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool False on failure, true on success
+ */
+function bbp_hide_forum( $forum_id = 0, $current_visibility = '' ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	do_action( 'bbp_hide_forum', $forum_id );
+
+	// Only run queries if visibility is changing
+	if ( bbp_get_hidden_status_id() !== $current_visibility ) {
+
+		// Get private forums
+		$private = bbp_get_private_forum_ids();
+
+		// Find this forum in the array
+		if ( in_array( $forum_id, $private, true ) ) {
+
+			$offset = array_search( $forum_id, $private, true );
+
+			// Splice around it
+			array_splice( $private, $offset, 1 );
+
+			// Update private forums minus this one
+			update_option( '_bbp_private_forums', bbp_get_unique_array_values( $private ) );
+		}
+
+		// Add to '_bbp_hidden_forums' site option
+		$hidden   = bbp_get_hidden_forum_ids();
+		$hidden[] = $forum_id;
+		update_option( '_bbp_hidden_forums', bbp_get_unique_array_values( $hidden ) );
+
+		// Update forums visibility setting
+		$bbp_db = bbp_db();
+		$bbp_db->update( $bbp_db->posts, array( 'post_status' => bbp_get_hidden_status_id() ), array( 'ID' => $forum_id ) );
+		wp_transition_post_status( bbp_get_hidden_status_id(), $current_visibility, get_post( $forum_id ) );
+		clean_post_cache( $forum_id );
+	}
+
+	do_action( 'bbp_hid_forum',  $forum_id );
+
+	return $forum_id;
+}
+
+/**
+ * Recaches the private and hidden forums
+ *
+ * @since 2.4.0 bbPress (r5017)
+ *
+ * @return array An array of the status code and the message
+ */
+function bbp_repair_forum_visibility() {
+
+	// First, delete everything.
+	delete_option( '_bbp_private_forums' );
+	delete_option( '_bbp_hidden_forums'  );
+
+	/**
+	 * Don't search for both private/hidden statuses. Since 'pre_get_posts' is an
+	 * action, it's not removed by suppress_filters. We need to make sure that
+	 * we're only searching for the supplied post_status.
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/2512
+	 */
+	remove_action( 'pre_get_posts', 'bbp_pre_get_posts_normalize_forum_visibility', 4 );
+
+	// Query for private forums
+	$private_forums = new WP_Query( array(
+		'fields'         => 'ids',
+		'post_type'      => bbp_get_forum_post_type(),
+		'post_status'    => bbp_get_private_status_id(),
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	// Query for hidden forums
+	$hidden_forums = new WP_Query( array(
+		'fields'           => 'ids',
+		'suppress_filters' => true,
+		'post_type'        => bbp_get_forum_post_type(),
+		'post_status'      => bbp_get_hidden_status_id(),
+		'posts_per_page'   => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	// Enable forum visibilty normalization
+	add_action( 'pre_get_posts', 'bbp_pre_get_posts_normalize_forum_visibility', 4 );
+
+	// Reset the $post global
+	wp_reset_postdata();
+
+	// Private
+	if ( ! is_wp_error( $private_forums ) ) {
+		update_option( '_bbp_private_forums', $private_forums->posts );
+	}
+
+	// Hidden forums
+	if ( ! is_wp_error( $hidden_forums ) ) {
+		update_option( '_bbp_hidden_forums',  $hidden_forums->posts  );
+	}
+
+	// Complete results
+	return true;
+}
+
+/** Subscriptions *************************************************************/
+
+/**
+ * Remove a deleted forum from all user subscriptions
+ *
+ * @since 2.5.0 bbPress (r5156)
+ *
+ * @param int $forum_id Get the forum ID to remove
+ */
+function bbp_remove_forum_from_all_subscriptions( $forum_id = 0 ) {
+
+	// Subscriptions are not active
+	if ( ! bbp_is_subscriptions_active() ) {
+		return;
+	}
+
+	// Bail if no forum
+	$forum_id = bbp_get_forum_id( $forum_id );
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// Remove forum from all subscriptions
+	return bbp_remove_object_from_all_users( $forum_id, '_bbp_subscription', 'post' );
+}
+
+/** Count Bumpers *************************************************************/
+
+/**
+ * Bump the total topic count of a forum
+ *
+ * @since 2.1.0 bbPress (r3825)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $difference Optional. Default 1
+ * @param bool $update_ancestors Optional. Default true
+ *
+ * @return int Forum topic count
+ */
+function bbp_bump_forum_topic_count( $forum_id = 0, $difference = 1, $update_ancestors = true ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get some counts
+	$forum_id          = bbp_get_forum_id( $forum_id );
+	$topic_count       = bbp_get_forum_topic_count( $forum_id, false, true );
+	$total_topic_count = bbp_get_forum_topic_count( $forum_id, true,  true );
+	$difference        = (int) $difference;
+
+	// Update this forum id
+	update_post_meta( $forum_id, '_bbp_topic_count',       (int) ( $topic_count       + $difference ) );
+	update_post_meta( $forum_id, '_bbp_total_topic_count', (int) ( $total_topic_count + $difference ) );
+
+	// Check for ancestors
+	if ( true === $update_ancestors ) {
+
+		// Get post ancestors
+		$forum     = get_post( $forum_id );
+		$ancestors = get_post_ancestors( $forum );
+
+		// If has ancestors, loop through them...
+		if ( ! empty( $ancestors ) ) {
+			foreach ( (array) $ancestors as $parent_forum_id ) {
+
+				// Only update topic count when an ancestor is not a category.
+				if ( ! bbp_is_forum_category( $parent_forum_id ) ) {
+
+					$parent_topic_count = bbp_get_forum_topic_count( $parent_forum_id, false, true );
+					update_post_meta( $parent_forum_id, '_bbp_topic_count', (int) ( $parent_topic_count + $difference ) );
+				}
+
+				// Update the total topic count.
+				$parent_total_topic_count = bbp_get_forum_topic_count( $parent_forum_id, true,  true );
+				update_post_meta( $parent_forum_id, '_bbp_total_topic_count', (int) ( $parent_total_topic_count + $difference ) );
+			}
+		}
+	}
+
+	$forum_topic_count = (int) ( $total_topic_count + $difference );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_forum_topic_count', $forum_topic_count, $forum_id, $difference, $update_ancestors );
+}
+
+/**
+ * Increase the total topic count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ * @return void
+ */
+function bbp_increase_forum_topic_count( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a topic, get the forum id.
+	if ( bbp_is_topic( $forum_id ) ) {
+		$topic_id = $forum_id;
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_topic_public( $topic_id ) ) {
+			bbp_increase_forum_topic_count_hidden( $forum_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_forum_topic_count( $forum_id );
+}
+
+/**
+ * Decrease the total topic count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_decrease_forum_topic_count( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a topic, get the forum id.
+	if ( bbp_is_topic( $forum_id ) ) {
+		$topic_id = $forum_id;
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_topic_public( $topic_id ) ) {
+			bbp_decrease_forum_topic_count_hidden( $forum_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_forum_topic_count( $forum_id, -1 );
+}
+
+/**
+ * Bump the total topic count of a forum
+ *
+ * @since 2.1.0 bbPress (r3825)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $difference Optional. Default 1
+ * @param bool $update_ancestors Optional. Default true
+ *
+ * @return int Forum topic count
+ */
+function bbp_bump_forum_topic_count_hidden( $forum_id = 0, $difference = 1, $update_ancestors = true ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get some counts
+	$forum_id          = bbp_get_forum_id( $forum_id );
+	$reply_count       = bbp_get_forum_topic_count_hidden( $forum_id, false, true );
+	$total_topic_count = bbp_get_forum_topic_count_hidden( $forum_id, true,  true );
+	$difference        = (int) $difference;
+
+	// Update this forum id
+	update_post_meta( $forum_id, '_bbp_topic_count_hidden',       (int) ( $reply_count       + $difference ) );
+	update_post_meta( $forum_id, '_bbp_total_topic_count_hidden', (int) ( $total_topic_count + $difference ) );
+
+	// Check for ancestors
+	if ( true === $update_ancestors ) {
+
+		// Get post ancestors
+		$forum     = get_post( $forum_id );
+		$ancestors = get_post_ancestors( $forum );
+
+		// If has ancestors, loop through them...
+		if ( ! empty( $ancestors ) ) {
+			foreach ( (array) $ancestors as $parent_forum_id ) {
+
+				// Only update topic count when an ancestor is not a category.
+				if ( ! bbp_is_forum_category( $parent_forum_id ) ) {
+
+					$parent_topic_count = bbp_get_forum_topic_count_hidden( $parent_forum_id, false, true );
+					update_post_meta( $parent_forum_id, '_bbp_topic_count_hidden', (int) ( $parent_topic_count + $difference ) );
+				}
+
+				// Update the total topic count.
+				$parent_total_topic_count = bbp_get_forum_topic_count_hidden( $parent_forum_id, true, true );
+				update_post_meta( $parent_forum_id, '_bbp_total_topic_count_hidden', (int) ( $parent_total_topic_count + $difference ) );
+			}
+		}
+	}
+
+	$forum_topic_count = (int) ( $total_topic_count + $difference );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_forum_topic_count_hidden', $forum_topic_count, $forum_id, $difference, $update_ancestors );
+}
+
+/**
+ * Increase the total hidden topic count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_increase_forum_topic_count_hidden( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a topic, get the forum id.
+	if ( bbp_is_topic( $forum_id ) ) {
+		$topic_id = $forum_id;
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_topic_public( $topic_id ) ) {
+			bbp_increase_forum_topic_count( $forum_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_forum_topic_count_hidden( $forum_id );
+}
+
+/**
+ * Decrease the total hidden topic count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_decrease_forum_topic_count_hidden( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a topic, get the forum id.
+	if ( bbp_is_topic( $forum_id ) ) {
+		$topic_id = $forum_id;
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_topic_public( $topic_id ) ) {
+			bbp_decrease_forum_topic_count( $forum_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_forum_topic_count_hidden( $forum_id, -1 );
+}
+
+/**
+ * Bump the total topic count of a forum
+ *
+ * @since 2.1.0 bbPress (r3825)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $difference Optional. Default 1
+ * @param bool $update_ancestors Optional. Default true
+ *
+ * @return int Forum topic count
+ */
+function bbp_bump_forum_reply_count( $forum_id = 0, $difference = 1, $update_ancestors = true ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get some counts
+	$forum_id          = bbp_get_forum_id( $forum_id );
+	$reply_count       = bbp_get_forum_reply_count( $forum_id, false, true );
+	$total_reply_count = bbp_get_forum_reply_count( $forum_id, true,  true );
+	$difference        = (int) $difference;
+
+	// Update this forum id
+	update_post_meta( $forum_id, '_bbp_reply_count',       (int) ( $reply_count       + $difference ) );
+	update_post_meta( $forum_id, '_bbp_total_reply_count', (int) ( $total_reply_count + $difference ) );
+
+	// Check for ancestors
+	if ( true === $update_ancestors ) {
+
+		// Get post ancestors
+		$forum     = get_post( $forum_id );
+		$ancestors = get_post_ancestors( $forum );
+
+		// If has ancestors, loop through them...
+		if ( ! empty( $ancestors ) ) {
+			foreach ( (array) $ancestors as $parent_forum_id ) {
+
+				// Only update reply count when an ancestor is not a category.
+				if ( ! bbp_is_forum_category( $parent_forum_id ) ) {
+
+					$parent_reply_count = bbp_get_forum_reply_count( $parent_forum_id, false, true );
+					update_post_meta( $parent_forum_id, '_bbp_reply_count', (int) ( $parent_reply_count + $difference ) );
+				}
+
+				// Update the total reply count.
+				$parent_total_reply_count = bbp_get_forum_reply_count( $parent_forum_id, true,  true );
+				update_post_meta( $parent_forum_id, '_bbp_total_reply_count', (int) ( $parent_total_reply_count + $difference ) );
+			}
+		}
+	}
+
+	$forum_reply_count = (int) ( $total_reply_count + $difference );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_forum_reply_count', $forum_reply_count, $forum_id, $difference, $update_ancestors );
+}
+
+/**
+ * Bump the total topic count of a forum
+ *
+ * @since 2.6.0 bbPress (r6922)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $difference Optional. Default 1
+ * @param bool $update_ancestors Optional. Default true
+ *
+ * @return int Forum topic count
+ */
+function bbp_bump_forum_reply_count_hidden( $forum_id = 0, $difference = 1, $update_ancestors = true ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get some counts
+	$forum_id          = bbp_get_forum_id( $forum_id );
+	$reply_count       = bbp_get_forum_reply_count_hidden( $forum_id, false, true );
+	$total_reply_count = bbp_get_forum_reply_count_hidden( $forum_id, true,  true );
+	$difference        = (int) $difference;
+
+	// Update this forum id
+	update_post_meta( $forum_id, '_bbp_reply_count_hidden',       (int) ( $reply_count       + $difference ) );
+	update_post_meta( $forum_id, '_bbp_total_reply_count_hidden', (int) ( $total_reply_count + $difference ) );
+
+	// Check for ancestors
+	if ( true === $update_ancestors ) {
+
+		// Get post ancestors
+		$forum     = get_post( $forum_id );
+		$ancestors = get_post_ancestors( $forum );
+
+		// If has ancestors, loop through them...
+		if ( ! empty( $ancestors ) ) {
+			foreach ( (array) $ancestors as $parent_forum_id ) {
+
+				// Only update reply count when an ancestor is not a category.
+				if ( ! bbp_is_forum_category( $parent_forum_id ) ) {
+
+					$parent_reply_count = bbp_get_forum_reply_count_hidden( $parent_forum_id, false, true );
+					update_post_meta( $parent_forum_id, '_bbp_reply_count_hidden', (int) ( $parent_reply_count + $difference ) );
+				}
+
+				// Update the total reply count.
+				$parent_total_reply_count = bbp_get_forum_reply_count_hidden( $parent_forum_id, true,  true );
+				update_post_meta( $parent_forum_id, '_bbp_total_reply_count_hidden', (int) ( $parent_total_reply_count + $difference ) );
+			}
+		}
+	}
+
+	$forum_reply_count = (int) ( $total_reply_count + $difference );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_forum_reply_count_hidden', $forum_reply_count, $forum_id, $difference, $update_ancestors );
+}
+
+/**
+ * Increase the total reply count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_increase_forum_reply_count( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the forum id.
+	if ( bbp_is_reply( $forum_id ) ) {
+		$reply_id = $forum_id;
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_reply_public( $reply_id ) ) {
+			bbp_increase_forum_reply_count_hidden( $forum_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_forum_reply_count( $forum_id );
+}
+
+/**
+ * Decrease the total reply count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_decrease_forum_reply_count( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the forum id.
+	if ( bbp_is_reply( $forum_id ) ) {
+		$reply_id = $forum_id;
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_reply_public( $reply_id ) ) {
+			bbp_decrease_forum_reply_count_hidden( $forum_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_forum_reply_count( $forum_id, -1 );
+}
+
+/**
+ * Increase the total hidden reply count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_increase_forum_reply_count_hidden( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the forum id.
+	if ( bbp_is_reply( $forum_id ) ) {
+		$reply_id = $forum_id;
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_reply_public( $reply_id ) ) {
+			bbp_increase_forum_reply_count( $forum_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_forum_reply_count_hidden( $forum_id );
+}
+
+/**
+ * Decrease the total hidden reply count of a forum by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_decrease_forum_reply_count_hidden( $forum_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the forum id.
+	if ( bbp_is_reply( $forum_id ) ) {
+		$reply_id = $forum_id;
+		$forum_id = bbp_get_reply_forum_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_reply_public( $reply_id ) ) {
+			bbp_decrease_forum_reply_count( $forum_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_forum_reply_count_hidden( $forum_id, -1 );
+}
+
+/**
+ * Update forum reply counts when a topic is approved or unapproved.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_approved_unapproved_topic_update_forum_reply_count( $topic_id = 0 ) {
+
+	// Bail early if we don't have a topic id.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// Get the topic's replies.
+	$count = bbp_get_public_child_count( $topic_id, bbp_get_reply_post_type() );
+
+	// If we're unapproving, set count to negative.
+	if ( 'bbp_unapproved_topic' === current_filter() ) {
+		$count = -$count;
+	}
+
+	// Bump up or down
+	bbp_bump_forum_reply_count( bbp_get_topic_forum_id( $topic_id ), $count );
+}
+
+/** Forum Updaters ************************************************************/
+
+/**
+ * Update the forum last topic id
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $topic_id Optional. Topic id.
+ * @return int Id of the forums most recent topic
+ */
+function bbp_update_forum_last_topic_id( $forum_id = 0, $topic_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Define local variable(s)
+	$children_last_topic = 0;
+
+	// Do some calculation if not manually set
+	if ( empty( $topic_id ) ) {
+
+		// Loop through children and add together forum reply counts
+		$children = bbp_forum_query_subforum_ids( $forum_id );
+		if ( ! empty( $children ) ) {
+			foreach ( $children as $child ) {
+				$children_last_topic = bbp_update_forum_last_topic_id( $child ); // Recursive
+			}
+		}
+
+		// Setup recent topic query vars
+		$post_vars = array(
+			'post_parent' => $forum_id,
+			'post_type'   => bbp_get_topic_post_type(),
+			'meta_key'    => '_bbp_last_active_time',
+			'meta_type'   => 'DATETIME',
+			'orderby'     => 'meta_value',
+			'numberposts' => 1
+		);
+
+		// Get the most recent topic in this forum_id
+		$recent_topic = get_posts( $post_vars );
+		if ( ! empty( $recent_topic ) ) {
+			$topic_id = $recent_topic[0]->ID;
+		}
+	}
+
+	// Cast as integer in case of empty or string
+	$topic_id            = (int) $topic_id;
+	$children_last_topic = (int) $children_last_topic;
+
+	// If child forums have higher id, use that instead
+	if ( ! empty( $children ) && ( $children_last_topic > $topic_id ) ) {
+		$topic_id = $children_last_topic;
+	}
+
+	// Update the last public topic ID
+	update_post_meta( $forum_id, '_bbp_last_topic_id', $topic_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_last_topic_id', $topic_id, $forum_id );
+}
+
+/**
+ * Update the forum last reply id
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $reply_id Optional. Reply id.
+ * @return int Id of the forums most recent reply
+ */
+function bbp_update_forum_last_reply_id( $forum_id = 0, $reply_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Define local variable(s)
+	$children_last_reply = 0;
+
+	// Do some calculation if not manually set
+	if ( empty( $reply_id ) ) {
+
+		// Loop through children and get the most recent reply id
+		$children = bbp_forum_query_subforum_ids( $forum_id );
+		if ( ! empty( $children ) ) {
+			foreach ( $children as $child ) {
+				$children_last_reply = bbp_update_forum_last_reply_id( $child ); // Recursive
+			}
+		}
+
+		// If this forum has topics...
+		$topic_ids = bbp_forum_query_topic_ids( $forum_id );
+		if ( ! empty( $topic_ids ) ) {
+
+			// ...get the most recent reply from those topics...
+			$reply_id = bbp_forum_query_last_reply_id( $forum_id, $topic_ids );
+
+			// ...and compare it to the most recent topic id...
+			$reply_id = ( $reply_id > max( $topic_ids ) )
+				? $reply_id
+				: max( $topic_ids );
+		}
+	}
+
+	// Cast as integer in case of empty or string
+	$reply_id            = (int) $reply_id;
+	$children_last_reply = (int) $children_last_reply;
+
+	// If child forums have higher ID, check for newer reply id
+	if ( ! empty( $children ) && ( $children_last_reply > $reply_id ) ) {
+		$reply_id = $children_last_reply;
+	}
+
+	// Update the last public reply ID
+	update_post_meta( $forum_id, '_bbp_last_reply_id', $reply_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_last_reply_id', $reply_id, $forum_id );
+}
+
+/**
+ * Update the forum last active post id
+ *
+ * @since 2.0.0 bbPress (r2860)
+ *
+ * @param int $forum_id Optional. Forum id.
+ * @param int $active_id Optional. Active post id.
+ * @return int Id of the forums last active post
+ */
+function bbp_update_forum_last_active_id( $forum_id = 0, $active_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Define local variable(s)
+	$children_last_active = 0;
+
+	// Do some calculation if not manually set
+	if ( empty( $active_id ) ) {
+
+		// Loop through children and get the last active ID
+		$children = bbp_forum_query_subforum_ids( $forum_id );
+		if ( ! empty( $children ) ) {
+			foreach ( $children as $child ) {
+				$children_last_active = bbp_update_forum_last_active_id( $child, $active_id );
+			}
+		}
+
+		// Get topic IDs and only accept larger IDs
+		$topic_ids = bbp_forum_query_topic_ids( $forum_id );
+		if ( ! empty( $topic_ids ) ) {
+
+			// Make sure ID is larger
+			$active_id = bbp_forum_query_last_reply_id( $forum_id, $topic_ids );
+			$active_id = $active_id > max( $topic_ids )
+				? $active_id
+				: max( $topic_ids );
+
+		// Forum has no topics
+		} else {
+			$active_id = 0;
+		}
+	}
+
+	// Cast as integer in case of empty or string
+	$active_id            = (int) $active_id;
+	$children_last_active = (int) $children_last_active;
+
+	// If child forums have higher ID, use that instead
+	if ( ! empty( $children ) && ( $children_last_active > $active_id ) ) {
+		$active_id = $children_last_active;
+	}
+
+	update_post_meta( $forum_id, '_bbp_last_active_id', $active_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_last_active_id', $active_id, $forum_id );
+}
+
+/**
+ * Update the forums last active date/time (aka freshness)
+ *
+ * @since 2.0.0 bbPress (r2680)
+ *
+ * @param int    $forum_id Optional. Topic id.
+ * @param string $new_time Optional. New time in mysql format.
+ *
+ * @return string MySQL timestamp of last active topic or reply
+ */
+function bbp_update_forum_last_active_time( $forum_id = 0, $new_time = '' ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Check time and use current if empty
+	if ( empty( $new_time ) ) {
+		$new_time = get_post_field( 'post_date', bbp_get_forum_last_active_id( $forum_id ) );
+	}
+
+	// Update only if there is a time
+	if ( ! empty( $new_time ) ) {
+		update_post_meta( $forum_id, '_bbp_last_active_time', $new_time );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_update_forum_last_active', $new_time, $forum_id );
+}
+
+/**
+ * Update the forum sub-forum count
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param int $subforums Optional. Number of subforums
+ * @return bool True on success, false on failure
+ */
+function bbp_update_forum_subforum_count( $forum_id = 0, $subforums = false ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Maybe query for counts
+	$subforums = ! is_int( $subforums )
+		? bbp_get_public_child_count( $forum_id, bbp_get_forum_post_type() )
+		: (int) $subforums;
+
+	update_post_meta( $forum_id, '_bbp_forum_subforum_count', $subforums );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_subforum_count', $subforums, $forum_id );
+}
+
+/**
+ * Adjust the total topic count of a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id or topic id. It is checked whether it
+ *                       is a topic or a forum. If it's a topic, its parent,
+ *                       i.e. the forum is automatically retrieved.
+ * @param bool $total_count Optional. To return the total count or normal count?
+ * @return int Forum topic count
+ */
+function bbp_update_forum_topic_count( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$children_topic_count = 0;
+
+	// Loop through subforums and add together forum topic counts
+	$children = bbp_forum_query_subforum_ids( $forum_id );
+	if ( ! empty( $children ) ) {
+		foreach ( $children as $child ) {
+			$children_topic_count += bbp_update_forum_topic_count( $child ); // Recursive
+		}
+	}
+
+	// Get total topics for this forum
+	$topics = bbp_get_public_child_count( $forum_id, bbp_get_topic_post_type() );
+
+	// Calculate total topics in this forum
+	$total_topics = (int) ( $topics + $children_topic_count );
+
+	// Update the count
+	update_post_meta( $forum_id, '_bbp_topic_count',       $topics       );
+	update_post_meta( $forum_id, '_bbp_total_topic_count', $total_topics );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_topic_count', $total_topics, $forum_id );
+}
+
+/**
+ * Adjust the total hidden topic count of a forum (hidden includes trashed,
+ * spammed and pending topics)
+ *
+ * @since 2.0.0 bbPress (r2888)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int $forum_id Optional. Topic id to update.
+ * @param int $topic_count Optional. Set the topic count manually.
+ *
+ * @return int Topic hidden topic count
+ */
+function bbp_update_forum_topic_count_hidden( $forum_id = 0, $topic_count = false ) {
+
+	// If topic_id was passed as $forum_id, then get its forum
+	if ( bbp_is_topic( $forum_id ) ) {
+		$topic_id = bbp_get_topic_id( $forum_id );
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+	// $forum_id is not a topic_id, so validate and proceed
+	} else {
+		$forum_id = bbp_get_forum_id( $forum_id );
+	}
+
+	// Can't update what isn't there
+	if ( ! empty( $forum_id ) ) {
+
+		// Get topics of forum
+		if ( ! is_int( $topic_count ) ) {
+			$query = new WP_Query( array(
+				'fields'         => 'ids',
+				'post_parent'    => $forum_id,
+				'post_status'    => bbp_get_non_public_topic_statuses(),
+				'post_type'      => bbp_get_topic_post_type(),
+				'posts_per_page' => -1,
+
+				// Performance
+				'nopaging'               => true,
+				'suppress_filters'       => true,
+				'update_post_term_cache' => false,
+				'update_post_meta_cache' => false,
+				'ignore_sticky_posts'    => true,
+				'no_found_rows'          => true
+			) );
+			$topic_count = $query->post_count;
+			unset( $query );
+		}
+
+		$topic_count = (int) $topic_count;
+
+		// Update the count
+		update_post_meta( $forum_id, '_bbp_topic_count_hidden', $topic_count );
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_topic_count_hidden', $topic_count, $forum_id );
+}
+
+/**
+ * Adjust the total reply count of a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int  $forum_id Optional. Forum id or topic id. It is checked whether it
+ *                       is a topic or a forum. If it's a topic, its parent,
+ *                       i.e. the forum is automatically retrieved.
+ *
+ * @return int Forum reply count
+ */
+function bbp_update_forum_reply_count( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$children_reply_count = 0;
+
+	// Loop through children and add together forum reply counts
+	$children = bbp_forum_query_subforum_ids( $forum_id );
+	if ( ! empty( $children ) ) {
+		foreach ( (array) $children as $child ) {
+			$children_reply_count += bbp_update_forum_reply_count( $child );
+		}
+	}
+
+	// Don't count replies if the forum is a category
+	$reply_count = ! bbp_is_forum_category( $forum_id )
+		? bbp_get_public_child_count( $forum_id, bbp_get_reply_post_type() )
+		: 0;
+
+	// Calculate total replies in this forum
+	$total_replies = (int) ( $reply_count + $children_reply_count );
+
+	// Update the counts
+	update_post_meta( $forum_id, '_bbp_reply_count',       $reply_count   );
+	update_post_meta( $forum_id, '_bbp_total_reply_count', $total_replies );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_reply_count', $total_replies, $forum_id );
+}
+
+/**
+ * Adjust the total hidden reply count of a forum
+ *
+ * @since 2.6.0 bbPress (r6922)
+ *
+ * @param int  $forum_id Optional. Forum id or topic id. It is checked whether it
+ *                       is a topic or a forum. If it's a topic, its parent,
+ *                       i.e. the forum is automatically retrieved.
+ *
+ * @return int Forum reply count
+ */
+function bbp_update_forum_reply_count_hidden( $forum_id = 0 ) {
+
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$children_reply_count = 0;
+
+	// Loop through children and add together forum reply counts
+	$children = bbp_forum_query_subforum_ids( $forum_id );
+	if ( ! empty( $children ) ) {
+		foreach ( (array) $children as $child ) {
+			$children_reply_count += bbp_update_forum_reply_count_hidden( $child );
+		}
+	}
+
+	// Don't count replies if the forum is a category
+	$reply_count = ! bbp_is_forum_category( $forum_id )
+		? bbp_get_non_public_child_count( $forum_id, bbp_get_reply_post_type() )
+		: 0;
+
+	// Calculate total replies in this forum
+	$total_replies = (int) ( $reply_count + $children_reply_count );
+
+	// Update the counts
+	update_post_meta( $forum_id, '_bbp_reply_count_hidden',       $reply_count   );
+	update_post_meta( $forum_id, '_bbp_total_reply_count_hidden', $total_replies );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_forum_reply_count_hidden', $total_replies, $forum_id );
+}
+
+/**
+ * Updates the counts of a forum.
+ *
+ * This calls a few internal functions that all run manual queries against the
+ * database to get their results. As such, this function can be costly to run
+ * but is necessary to keep everything accurate.
+ *
+ * @since 2.0.0 bbPress (r2908)
+ *
+ * @param array $args Supports these arguments:
+ *  - forum_id: Forum id
+ *  - last_topic_id: Last topic id
+ *  - last_reply_id: Last reply id
+ *  - last_active_id: Last active post id
+ *  - last_active_time: last active time
+ */
+function bbp_update_forum( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'forum_id'           => 0,
+		'post_parent'        => 0,
+		'last_topic_id'      => 0,
+		'last_reply_id'      => 0,
+		'last_active_id'     => 0,
+		'last_active_time'   => 0,
+		'last_active_status' => bbp_get_public_status_id()
+	), 'update_forum' );
+
+	// Update the forum parent
+	bbp_update_forum_id( $r['forum_id'], $r['post_parent'] );
+
+	// Last topic and reply ID's
+	bbp_update_forum_last_topic_id( $r['forum_id'], $r['last_topic_id'] );
+	bbp_update_forum_last_reply_id( $r['forum_id'], $r['last_reply_id'] );
+
+	// Active dance
+	$r['last_active_id'] = bbp_update_forum_last_active_id( $r['forum_id'], $r['last_active_id'] );
+
+	// If no active time was passed, get it from the last_active_id
+	if ( empty( $r['last_active_time'] ) ) {
+		$r['last_active_time'] = get_post_field( 'post_date', $r['last_active_id'] );
+	}
+
+	if ( bbp_get_public_status_id() === $r['last_active_status'] ) {
+		bbp_update_forum_last_active_time( $r['forum_id'], $r['last_active_time'] );
+	}
+
+	// Counts
+	bbp_update_forum_subforum_count( $r['forum_id'] );
+
+	// Only update topic count if we've deleted a topic
+	if ( in_array( current_filter(), array( 'bbp_deleted_topic', 'save_post' ), true ) ) {
+		bbp_update_forum_reply_count(        $r['forum_id'] );
+		bbp_update_forum_topic_count(        $r['forum_id'] );
+		bbp_update_forum_topic_count_hidden( $r['forum_id'] );
+		bbp_update_forum_reply_count_hidden( $r['forum_id'] );
+	}
+
+	// Update the parent forum if one was passed
+	if ( ! empty( $r['post_parent'] ) && is_numeric( $r['post_parent'] ) ) {
+		bbp_update_forum( array(
+			'forum_id'    => $r['post_parent'],
+			'post_parent' => get_post_field( 'post_parent', $r['post_parent'] )
+		) );
+	}
+
+	// Bump the custom query cache
+	wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Return an associative array of available topic statuses
+ *
+ * Developers note: these statuses are actually stored as meta data, and
+ * Visibilities are stored in post_status.
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param int $forum_id   Optional. Forum id.
+ *
+ * @return array
+ */
+function bbp_get_forum_statuses( $forum_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_statuses', array(
+		'open'   => _x( 'Open',    'Open the forum',  'bbpress' ),
+		'closed' => _x( 'Closed',  'Close the forum', 'bbpress' )
+	), $forum_id );
+}
+
+/**
+ * Return an associative array of forum types
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param int $forum_id   Optional. Forum id.
+ *
+ * @return array
+ */
+function bbp_get_forum_types( $forum_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_types', array(
+		'forum'    => _x( 'Forum',    'Forum accepts new topics', 'bbpress' ),
+		'category' => _x( 'Category', 'Forum is a category',      'bbpress' )
+	), $forum_id );
+}
+
+/**
+ * Return an associative array of forum visibility
+ *
+ * Developers note: these visibilities are actually stored in post_status, and
+ * Statuses are stored in meta data.
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param int $forum_id   Optional. Forum id.
+ *
+ * @return array
+ */
+function bbp_get_forum_visibilities( $forum_id = 0) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_visibilities', array(
+		bbp_get_public_status_id()  => _x( 'Public',  'Make forum public',  'bbpress' ),
+		bbp_get_private_status_id() => _x( 'Private', 'Make forum private', 'bbpress' ),
+		bbp_get_hidden_status_id()  => _x( 'Hidden',  'Make forum hidden',  'bbpress' )
+	), $forum_id );
+}
+
+/**
+ * Return array of public forum statuses.
+ *
+ * @since 2.6.0 bbPress (r6921)
+ *
+ * @return array
+ */
+function bbp_get_public_forum_statuses() {
+	$statuses = array(
+		bbp_get_public_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_public_forum_statuses', $statuses );
+}
+
+/**
+ * Return array of non-public forum statuses.
+ *
+ * @since 2.6.0 bbPress (r6921)
+ *
+ * @return array
+ */
+function bbp_get_non_public_forum_statuses() {
+	$statuses = array(
+		bbp_get_private_status_id(),
+		bbp_get_hidden_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_non_public_forum_statuses', $statuses );
+}
+
+/** Queries *******************************************************************/
+
+/**
+ * Returns the hidden forum ids
+ *
+ * Only hidden forum ids are returned. Public and private ids are not.
+ *
+ * @since 2.0.0 bbPress (r3007)
+ */
+function bbp_get_hidden_forum_ids() {
+	$forum_ids = get_option( '_bbp_hidden_forums', array() );
+	$forum_ids = ! empty( $forum_ids )
+		? wp_parse_id_list( $forum_ids )
+		: array();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_hidden_forum_ids', $forum_ids );
+}
+
+/**
+ * Returns the private forum ids
+ *
+ * Only private forum ids are returned. Public and hidden ids are not.
+ *
+ * @since 2.0.0 bbPress (r3007)
+ */
+function bbp_get_private_forum_ids() {
+	$forum_ids = get_option( '_bbp_private_forums', array() );
+	$forum_ids = ! empty( $forum_ids )
+		? wp_parse_id_list( $forum_ids )
+		: array();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_private_forum_ids', $forum_ids );
+}
+
+/**
+ * Returns the forum IDs that should be excluded from various views & queries,
+ * based on the current user's capabilities.
+ *
+ * These results are automatically filtered by bbp_allow_forums_of_user(), to
+ * allow per-forum moderators to see forums that would otherwise be private or
+ * hidden to them.
+ *
+ * If you have a need to filter these results based on your own custom
+ * engagements API usages, please see: bbp_allow_forums_of_user()
+ *
+ * @since 2.6.0 bbPress (r6425)
+ *
+ * @return array Forum IDs to exclude, or an empty array
+ */
+function bbp_get_excluded_forum_ids() {
+
+	// Private forums
+	$private = ! current_user_can( 'read_private_forums' )
+		? bbp_get_private_forum_ids()
+		: array();
+
+	// Hidden forums
+	$hidden = ! current_user_can( 'read_hidden_forums' )
+		? bbp_get_hidden_forum_ids()
+		: array();
+
+	// Merge private & hidden forums together, and remove any empties
+	$forum_ids = ( ! empty( $private ) || ! empty( $hidden ) )
+		? array_filter( wp_parse_id_list( array_merge( $private, $hidden ) ) )
+		: array();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_excluded_forum_ids', $forum_ids, $private, $hidden );
+}
+
+/**
+ * Returns a meta_query that either includes or excludes hidden forum IDs
+ * from a query.
+ *
+ * @since 2.0.0 bbPress (r3291)
+ *
+ * @param string Optional. The type of value to return. (string|array|meta_query)
+ */
+function bbp_exclude_forum_ids( $type = 'string' ) {
+
+	// Setup arrays
+	$forum_ids = array();
+
+	// Types
+	$types = array(
+		'array'      => array(),
+		'string'     => '',
+		'meta_query' => array()
+	);
+
+	// Exclude for everyone but keymasters
+	if ( ! bbp_is_user_keymaster() ) {
+
+		// Get forum IDs to exclude
+		$forum_ids = bbp_get_excluded_forum_ids();
+
+		// Store return values in static types array
+		if ( ! empty( $forum_ids ) ) {
+
+			// Comparison
+			$compare = ( 1 < count( $forum_ids ) )
+				? 'NOT IN'
+				: '!=';
+
+			// Setup types
+			$types['array']      = $forum_ids;
+			$types['string']     = implode( ',', $forum_ids );
+			$types['meta_query'] = array(
+				'key'     => '_bbp_forum_id',
+				'value'   => $types['string'],
+				'type'    => 'NUMERIC',
+				'compare' => $compare
+			);
+		}
+	}
+
+	// There are forums that need to be excluded
+	$retval = $types[ $type ];
+
+	// Filter & return
+	return apply_filters( 'bbp_exclude_forum_ids', $retval, $forum_ids, $type );
+}
+
+/**
+ * Adjusts forum, topic, and reply queries to exclude items that might be
+ * contained inside hidden or private forums that the user does not have the
+ * capability to view.
+ *
+ * Doing it with an action allows us to trap all WP_Query's rather than needing
+ * to hardcode this logic into each query. It also protects forum content for
+ * plugins that might be doing their own queries.
+ *
+ * @since 2.0.0 bbPress (r3291)
+ *
+ * @param WP_Query $posts_query
+ *
+ * @return WP_Query
+ */
+function bbp_pre_get_posts_normalize_forum_visibility( $posts_query = null ) {
+
+	// Bail if all forums are explicitly allowed
+	if ( true === apply_filters( 'bbp_include_all_forums', false, $posts_query ) ) {
+		return;
+	}
+
+	// Bail if $posts_query is not an object or of incorrect class
+	if ( ! is_object( $posts_query ) || ! is_a( $posts_query, 'WP_Query' ) ) {
+		return;
+	}
+
+	// Get query post types array .
+	$post_types = (array) $posts_query->get( 'post_type' );
+
+	// Forums
+	if ( bbp_get_forum_post_type() === implode( '', $post_types ) ) {
+
+		// Prevent accidental wp-admin post_row override
+		if ( is_admin() && isset( $_REQUEST['post_status'] ) ) {
+			return;
+		}
+
+		/** Default ***********************************************************/
+
+		// Add all supported forum visibilities
+		$posts_query->set( 'post_status', array_keys( bbp_get_forum_visibilities() ) );
+
+		// Get forums to exclude
+		$hidden_ids = bbp_exclude_forum_ids( 'array' );
+
+		// Bail if no forums to exclude
+		if ( empty( $hidden_ids ) ) {
+			return;
+		}
+
+		// Get any existing meta queries
+		$not_in = $posts_query->get( 'post__not_in', array() );
+
+		// Add our meta query to existing
+		$not_in = array_unique( array_merge( $not_in, $hidden_ids ) );
+
+		// Set the meta_query var
+		$posts_query->set( 'post__not_in', $not_in );
+
+	// Some other post type besides Forums, Topics, or Replies
+	} elseif ( ! array_diff( $post_types, bbp_get_post_types() ) ) {
+
+		// Get forums to exclude
+		$forum_ids = bbp_exclude_forum_ids( 'meta_query' );
+
+		// Bail if no forums to exclude
+		if ( empty( $forum_ids ) ) {
+			return;
+		}
+
+		// Get any existing meta queries
+		$meta_query   = (array) $posts_query->get( 'meta_query', array() );
+
+		// Add our meta query to existing
+		$meta_query[] = $forum_ids;
+
+		// Set the meta_query var
+		$posts_query->set( 'meta_query', $meta_query );
+	}
+}
+
+/**
+ * Returns the forum's topic ids
+ *
+ * Only topics with published and closed statuses are returned
+ *
+ * @since 2.0.0 bbPress (r2908)
+ *
+ * @param int $forum_id Forum id
+ */
+function bbp_forum_query_topic_ids( $forum_id ) {
+	$topic_ids = bbp_get_public_child_ids( $forum_id, bbp_get_topic_post_type() );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_forum_query_topic_ids', $topic_ids, $forum_id );
+}
+
+/**
+ * Returns the forum's subforum ids
+ *
+ * Only forums with published status are returned
+ *
+ * @since 2.0.0 bbPress (r2908)
+ *
+ * @param int $forum_id Forum id
+ */
+function bbp_forum_query_subforum_ids( $forum_id ) {
+	$subforum_ids = bbp_get_all_child_ids( $forum_id, bbp_get_forum_post_type() );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_forum_query_subforum_ids', $subforum_ids, $forum_id );
+}
+
+/**
+ * Returns the forum's last reply id
+ *
+ * @since 2.0.0 bbPress (r2908)
+ * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
+ *
+ * @param int $forum_id Forum id.
+ * @param int $topic_ids Optional. Topic ids.
+ */
+function bbp_forum_query_last_reply_id( $forum_id = 0, $topic_ids = 0 ) {
+
+	// Validate forum
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Get topic ID's if none were passed
+	if ( empty( $topic_ids ) ) {
+		$topic_ids = bbp_forum_query_topic_ids( $forum_id );
+	}
+
+	$query = new WP_Query( array(
+		'fields'           => 'ids',
+		'suppress_filters' => true,
+		'post_parent__in'  => $topic_ids,
+		'post_status'      => bbp_get_public_status_id(),
+		'post_type'        => bbp_get_reply_post_type(),
+		'posts_per_page'   => 1,
+		'orderby'          => array(
+			'post_date' => 'DESC',
+			'ID'        => 'DESC'
+		),
+
+		// Performance
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	$reply_id = array_shift( $query->posts );
+
+	unset( $query );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_forum_query_last_reply_id', $reply_id, $forum_id );
+}
+
+/** Listeners *****************************************************************/
+
+/**
+ * Check if it's a hidden forum or a topic or reply of a hidden forum and if
+ * the user can't view it, then sets a 404
+ *
+ * @since 2.0.0 bbPress (r2996)
+ */
+function bbp_forum_enforce_hidden() {
+
+	// Bail if not viewing a single item or if user has caps
+	if ( ! is_singular() || bbp_is_user_keymaster() || current_user_can( 'read_hidden_forums' ) ) {
+		return;
+	}
+
+	// Define local variables
+	$forum_id = 0;
+	$wp_query = bbp_get_wp_query();
+
+	// Check post type
+	switch ( $wp_query->get( 'post_type' ) ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$forum_id = bbp_get_forum_id( $wp_query->post->ID );
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$forum_id = bbp_get_topic_forum_id( $wp_query->post->ID );
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+			$forum_id = bbp_get_reply_forum_id( $wp_query->post->ID );
+			break;
+	}
+
+	// If forum is explicitly hidden and user not capable, set 404
+	if ( ! empty( $forum_id ) && bbp_is_forum_hidden( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+		bbp_set_404( $wp_query );
+	}
+}
+
+/**
+ * Check if it's a private forum or a topic or reply of a private forum and if
+ * the user can't view it, then sets a 404
+ *
+ * @since 2.0.0 bbPress (r2996)
+ */
+function bbp_forum_enforce_private() {
+
+	// Bail if not viewing a single item or if user has caps
+	if ( ! is_singular() || bbp_is_user_keymaster() || current_user_can( 'read_private_forums' ) ) {
+		return;
+	}
+
+	// Define local variables
+	$forum_id = 0;
+	$wp_query = bbp_get_wp_query();
+
+	// Check post type
+	switch ( $wp_query->get( 'post_type' ) ) {
+
+		// Forum
+		case bbp_get_forum_post_type() :
+			$forum_id = bbp_get_forum_id( $wp_query->post->ID );
+			break;
+
+		// Topic
+		case bbp_get_topic_post_type() :
+			$forum_id = bbp_get_topic_forum_id( $wp_query->post->ID );
+			break;
+
+		// Reply
+		case bbp_get_reply_post_type() :
+			$forum_id = bbp_get_reply_forum_id( $wp_query->post->ID );
+			break;
+
+	}
+
+	// If forum is explicitly hidden and user not capable, set 404
+	if ( ! empty( $forum_id ) && bbp_is_forum_private( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+		bbp_set_404( $wp_query );
+	}
+}
+
+/** Permissions ***************************************************************/
+
+/**
+ * Redirect if unauthorized user is attempting to edit a forum
+ *
+ * @since 2.1.0 bbPress (r3607)
+ */
+function bbp_check_forum_edit() {
+
+	// Bail if not editing a topic
+	if ( ! bbp_is_forum_edit() ) {
+		return;
+	}
+
+	// User cannot edit topic, so redirect back to reply
+	if ( ! current_user_can( 'edit_forum', bbp_get_forum_id() ) ) {
+		bbp_redirect( bbp_get_forum_permalink() );
+	}
+}
+
+/**
+ * Delete all topics (and their replies) for a specific forum ID
+ *
+ * @since 2.1.0 bbPress (r3668)
+ *
+ * @param int $forum_id
+ * @return If forum is not valid
+ */
+function bbp_delete_forum_topics( $forum_id = 0 ) {
+
+	// Validate forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// Forum is being permanently deleted, so its content has go too
+	// Note that we get all post statuses here
+	$topics = new WP_Query( array(
+		'fields'         => 'id=>parent',
+		'post_type'      => bbp_get_topic_post_type(),
+		'post_parent'    => $forum_id,
+		'post_status'    => array_keys( get_post_stati() ),
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	// Loop through and delete child topics. Topic replies will get deleted by
+	// the bbp_delete_topic() action.
+	if ( ! empty( $topics->posts ) ) {
+		foreach ( $topics->posts as $topic ) {
+			wp_delete_post( $topic->ID, true );
+		}
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+
+	// Cleanup
+	unset( $topics );
+}
+
+/**
+ * Trash all topics inside a forum
+ *
+ * @since 2.1.0 bbPress (r3668)
+ *
+ * @param int $forum_id
+ * @return If forum is not valid
+ */
+function bbp_trash_forum_topics( $forum_id = 0 ) {
+
+	// Validate forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// Allowed post statuses to pre-trash
+	$post_stati = array(
+		bbp_get_public_status_id(),
+		bbp_get_closed_status_id(),
+		bbp_get_pending_status_id()
+	);
+
+	// Forum is being trashed, so its topics (and replies) are trashed too
+	$topics = new WP_Query( array(
+		'fields'         => 'id=>parent',
+		'post_type'      => bbp_get_topic_post_type(),
+		'post_parent'    => $forum_id,
+		'post_status'    => $post_stati,
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	// Loop through and trash child topics. Topic replies will get trashed by
+	// the bbp_trash_topic() action.
+	if ( ! empty( $topics->posts ) ) {
+
+		// Prevent debug notices
+		$pre_trashed_topics = array();
+
+		// Loop through topics, trash them, and add them to array
+		foreach ( $topics->posts as $topic ) {
+			wp_trash_post( $topic->ID, true );
+			$pre_trashed_topics[] = $topic->ID;
+		}
+
+		// Set a post_meta entry of the topics that were trashed by this action.
+		// This is so we can possibly untrash them, without untrashing topics
+		// that were purposefully trashed before.
+		update_post_meta( $forum_id, '_bbp_pre_trashed_topics', $pre_trashed_topics );
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+
+	// Cleanup
+	unset( $topics );
+}
+
+/**
+ * Untrash all topics inside a forum
+ *
+ * @since 2.1.0 bbPress (r3668)
+ *
+ * @param int $forum_id
+ * @return If forum is not valid
+ */
+function bbp_untrash_forum_topics( $forum_id = 0 ) {
+
+	// Validate forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) ) {
+		return;
+	}
+
+	// Get the topics that were not previously trashed
+	$pre_trashed_topics = get_post_meta( $forum_id, '_bbp_pre_trashed_topics', true );
+
+	// There are topics to untrash
+	if ( ! empty( $pre_trashed_topics ) ) {
+
+		// Maybe reverse the trashed topics array
+		if ( is_array( $pre_trashed_topics ) ) {
+			$pre_trashed_topics = array_reverse( $pre_trashed_topics );
+		}
+
+		// Loop through topics
+		foreach ( (array) $pre_trashed_topics as $topic ) {
+			wp_untrash_post( $topic );
+		}
+	}
+}
+
+/** Before Delete/Trash/Untrash ***********************************************/
+
+/**
+ * Called before deleting a forum.
+ *
+ * This function is supplemental to the actual forum deletion which is
+ * handled by WordPress core API functions. It is used to clean up after
+ * a forum that is being deleted.
+ *
+ * @since 2.1.0 bbPress (r3668)
+ */
+function bbp_delete_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_delete_forum', $forum_id );
+}
+
+/**
+ * Called before trashing a forum
+ *
+ * This function is supplemental to the actual forum being trashed which is
+ * handled by WordPress core API functions. It is used to clean up after
+ * a forum that is being trashed.
+ *
+ * @since 2.1.0 bbPress (r3668)
+ */
+function bbp_trash_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trash_forum', $forum_id );
+}
+
+/**
+ * Called before untrashing a forum
+ *
+ * @since 2.1.0 bbPress (r3668)
+ */
+function bbp_untrash_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrash_forum', $forum_id );
+}
+
+/** After Delete/Trash/Untrash ************************************************/
+
+/**
+ * Called after deleting a forum
+ *
+ * Try not to use this action. All meta & taxonomy terms have already been
+ * deleted, making them impossible to use.
+ *
+ * @since 2.1.0 bbPress (r3668)
+ * @since 2.6.0 bbPress (r6526) Not recommend for usage
+ */
+function bbp_deleted_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_deleted_forum', $forum_id );
+}
+
+/**
+ * Called after trashing a forum
+ *
+ * @since 2.1.0 bbPress (r3668)
+ */
+function bbp_trashed_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trashed_forum', $forum_id );
+}
+
+/**
+ * Called after untrashing a forum
+ *
+ * @since 2.1.0 bbPress (r3668)
+ */
+function bbp_untrashed_forum( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	if ( empty( $forum_id ) || ! bbp_is_forum( $forum_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrashed_forum', $forum_id );
+}
Index: /tags/2.6.6/src/includes/forums/index.php
===================================================================
--- /tags/2.6.6/src/includes/forums/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/forums/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/forums/template.php
===================================================================
--- /tags/2.6.6/src/includes/forums/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/forums/template.php	(revision 7162)
@@ -0,0 +1,2719 @@
+<?php
+
+/**
+ * bbPress Forum Template Tags
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Post Type *****************************************************************/
+
+/**
+ * Output the unique id of the custom post type for forums
+ *
+ * @since 2.0.0 bbPress (r2857)
+ */
+function bbp_forum_post_type() {
+	echo bbp_get_forum_post_type();
+}
+	/**
+	 * Return the unique id of the custom post type for forums
+	 *
+	 * @since 2.0.0 bbPress (r2857)
+	 *
+	 * @return string The unique forum post type id
+	 */
+	function bbp_get_forum_post_type() {
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_post_type', bbpress()->forum_post_type );
+	}
+
+
+/**
+ * Return array of labels used by the forum post type
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_forum_post_type_labels() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_post_type_labels', array(
+		'name'                     => esc_attr__( 'Forums',                     'bbpress' ),
+		'menu_name'                => esc_attr__( 'Forums',                     'bbpress' ),
+		'singular_name'            => esc_attr__( 'Forum',                      'bbpress' ),
+		'all_items'                => esc_attr__( 'All Forums',                 'bbpress' ),
+		'add_new'                  => esc_attr__( 'Add New',                    'bbpress' ),
+		'add_new_item'             => esc_attr__( 'Create New Forum',           'bbpress' ),
+		'edit'                     => esc_attr__( 'Edit',                       'bbpress' ),
+		'edit_item'                => esc_attr__( 'Edit Forum',                 'bbpress' ),
+		'new_item'                 => esc_attr__( 'New Forum',                  'bbpress' ),
+		'view'                     => esc_attr__( 'View Forum',                 'bbpress' ),
+		'view_item'                => esc_attr__( 'View Forum',                 'bbpress' ),
+		'view_items'               => esc_attr__( 'View Forums',                'bbpress' ),
+		'search_items'             => esc_attr__( 'Search Forums',              'bbpress' ),
+		'not_found'                => esc_attr__( 'No forums found',            'bbpress' ),
+		'not_found_in_trash'       => esc_attr__( 'No forums found in Trash',   'bbpress' ),
+		'filter_items_list'        => esc_attr__( 'Filter forums list',         'bbpress' ),
+		'items_list'               => esc_attr__( 'Forums list',                'bbpress' ),
+		'items_list_navigation'    => esc_attr__( 'Forums list navigation',     'bbpress' ),
+		'parent_item_colon'        => esc_attr__( 'Parent Forum:',              'bbpress' ),
+		'archives'                 => esc_attr__( 'Forums',                     'bbpress' ),
+		'attributes'               => esc_attr__( 'Forum Attributes',           'bbpress' ),
+		'insert_into_item'         => esc_attr__( 'Insert into forum',          'bbpress' ),
+		'uploaded_to_this_item'    => esc_attr__( 'Uploaded to this forum',     'bbpress' ),
+		'featured_image'           => esc_attr__( 'Forum Image',                'bbpress' ),
+		'set_featured_image'       => esc_attr__( 'Set forum image',            'bbpress' ),
+		'remove_featured_image'    => esc_attr__( 'Remove forum image',         'bbpress' ),
+		'use_featured_image'       => esc_attr__( 'Use as forum image',         'bbpress' ),
+		'item_published'           => esc_attr__( 'Forum published.',           'bbpress' ),
+		'item_published_privately' => esc_attr__( 'Forum published privately.', 'bbpress' ),
+		'item_reverted_to_draft'   => esc_attr__( 'Forum reverted to draft.',   'bbpress' ),
+		'item_scheduled'           => esc_attr__( 'Forum scheduled.',           'bbpress' ),
+		'item_updated'             => esc_attr__( 'Forum updated.',             'bbpress' )
+	) );
+}
+
+/**
+ * Return array of forum post type rewrite settings
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_forum_post_type_rewrite() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_post_type_rewrite', array(
+		'slug'       => bbp_get_forum_slug(),
+		'with_front' => false
+	) );
+}
+
+/**
+ * Return array of features the forum post type supports
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_forum_post_type_supports() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_post_type_supports', array(
+		'title',
+		'editor',
+		'revisions'
+	) );
+}
+
+/** Forum Loop ****************************************************************/
+
+/**
+ * The main forum loop.
+ *
+ * WordPress makes this easy for us.
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param array $args All the arguments supported by {@link WP_Query}
+ *
+ * @return object Multidimensional array of forum information
+ */
+function bbp_has_forums( $args = array() ) {
+
+	// Forum archive only shows root
+	if ( bbp_is_forum_archive() ) {
+		$default_post_parent = 0;
+
+	// User subscriptions shows any
+	} elseif ( bbp_is_subscriptions() ) {
+		$default_post_parent = 'any';
+
+	// Could be anything, so look for possible parent ID
+	} else {
+		$default_post_parent = bbp_get_forum_id();
+	}
+
+	$default_forum_search = bbp_sanitize_search_request( 'fs' );
+
+	// Default argument array
+	$default = array(
+		'post_type'           => bbp_get_forum_post_type(),
+		'post_parent'         => $default_post_parent,
+		'post_status'         => bbp_get_public_status_id(),
+		'posts_per_page'      => get_option( '_bbp_forums_per_page', 50 ),
+		'orderby'             => 'menu_order title',
+		'order'               => 'ASC',
+		'no_found_rows'       => true,
+		'ignore_sticky_posts' => true,
+
+		// Conditionally prime the cache for last active posts
+		'update_post_family_cache' => true
+	);
+
+	// Only add 's' arg if searching for forums
+	// See https://bbpress.trac.wordpress.org/ticket/2607
+	if ( ! empty( $default_forum_search ) ) {
+		$default['s'] = $default_forum_search;
+	}
+
+	// Parse arguments with default forum query for most circumstances
+	$r = bbp_parse_args( $args, $default, 'has_forums' );
+
+	// Run the query
+	$bbp              = bbpress();
+	$bbp->forum_query = new WP_Query( $r );
+
+	// Maybe prime last active posts
+	if ( ! empty( $r['update_post_family_cache'] ) ) {
+		bbp_update_post_family_caches( $bbp->forum_query->posts );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_has_forums', $bbp->forum_query->have_posts(), $bbp->forum_query );
+}
+
+/**
+ * Whether there are more forums available in the loop
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @return object Forum information
+ */
+function bbp_forums() {
+
+	// Put into variable to check against next
+	$have_posts = bbpress()->forum_query->have_posts();
+
+	// Reset the post data when finished
+	if ( empty( $have_posts ) ) {
+		wp_reset_postdata();
+	}
+
+	return $have_posts;
+}
+
+/**
+ * Loads up the current forum in the loop
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @return object Forum information
+ */
+function bbp_the_forum() {
+	return bbpress()->forum_query->the_post();
+}
+
+/** Forum *********************************************************************/
+
+/**
+ * Output forum id
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param $forum_id Optional. Used to check emptiness
+ */
+function bbp_forum_id( $forum_id = 0 ) {
+	echo bbp_get_forum_id( $forum_id );
+}
+	/**
+	 * Return the forum id
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param $forum_id Optional. Used to check emptiness
+	 * @return int The forum id
+	 */
+	function bbp_get_forum_id( $forum_id = 0 ) {
+		$bbp      = bbpress();
+		$wp_query = bbp_get_wp_query();
+
+		// Easy empty checking
+		if ( ! empty( $forum_id ) && is_numeric( $forum_id ) ) {
+			$bbp_forum_id = $forum_id;
+
+		// Currently inside a forum loop
+		} elseif ( ! empty( $bbp->forum_query->in_the_loop ) && isset( $bbp->forum_query->post->ID ) ) {
+			$bbp_forum_id = $bbp->forum_query->post->ID;
+
+		// Currently inside a search loop
+		} elseif ( ! empty( $bbp->search_query->in_the_loop ) && isset( $bbp->search_query->post->ID ) && bbp_is_forum( $bbp->search_query->post->ID ) ) {
+			$bbp_forum_id = $bbp->search_query->post->ID;
+
+		// Currently viewing a forum
+		} elseif ( ( bbp_is_single_forum() || bbp_is_forum_edit() ) && ! empty( $bbp->current_forum_id ) ) {
+			$bbp_forum_id = $bbp->current_forum_id;
+
+		// Currently viewing a forum
+		} elseif ( ( bbp_is_single_forum() || bbp_is_forum_edit() ) && isset( $wp_query->post->ID ) ) {
+			$bbp_forum_id = $wp_query->post->ID;
+
+		// Currently viewing a topic
+		} elseif ( bbp_is_single_topic() ) {
+			$bbp_forum_id = bbp_get_topic_forum_id();
+
+		// Fallback
+		} else {
+			$bbp_forum_id = 0;
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_id', (int) $bbp_forum_id, $forum_id );
+	}
+
+/**
+ * Gets a forum
+ *
+ * @since 2.0.0 bbPress (r2787)
+ *
+ * @param int|object $forum forum id or forum object
+ * @param string $output Optional. OBJECT, ARRAY_A, or ARRAY_N. Default = OBJECT
+ * @param string $filter Optional Sanitation filter. See {@link sanitize_post()}
+ *
+ * @return mixed Null if error or forum (in specified form) if success
+ */
+function bbp_get_forum( $forum, $output = OBJECT, $filter = 'raw' ) {
+
+	// Maybe get ID from empty or int
+	if ( empty( $forum ) || is_numeric( $forum ) ) {
+		$forum = bbp_get_forum_id( $forum );
+	}
+
+	// Bail if no post object
+	$forum = get_post( $forum, OBJECT, $filter );
+	if ( empty( $forum ) ) {
+		return $forum;
+	}
+
+	// Bail if not correct post type
+	if ( $forum->post_type !== bbp_get_forum_post_type() ) {
+		return null;
+	}
+
+	// Default return value is OBJECT
+	$retval = $forum;
+
+	// Array A
+	if ( $output === ARRAY_A ) {
+		$retval = get_object_vars( $forum );
+
+	// Array N
+	} elseif ( $output === ARRAY_N ) {
+		$retval = array_values( get_object_vars( $forum ) );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_forum', $retval, $forum, $output, $filter );
+}
+
+/**
+ * Output the link to the forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param string $redirect_to Optional. Pass a redirect value for use with
+ *                              shortcodes and other fun things.
+ */
+function bbp_forum_permalink( $forum_id = 0, $redirect_to = '' ) {
+	echo esc_url( bbp_get_forum_permalink( $forum_id, $redirect_to ) );
+}
+	/**
+	 * Return the link to the forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param string $redirect_to Optional. Pass a redirect value for use with
+	 *                              shortcodes and other fun things.
+	 *
+	 * @return string Permanent link to forum
+	 */
+	function bbp_get_forum_permalink( $forum_id = 0, $redirect_to = '' ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Use the redirect address
+		if ( ! empty( $redirect_to ) ) {
+			$forum_permalink = esc_url_raw( $redirect_to );
+
+		// Use the topic permalink
+		} else {
+			$forum_permalink = get_permalink( $forum_id );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_permalink', $forum_permalink, $forum_id );
+	}
+
+/**
+ * Output the title of the forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_title( $forum_id = 0 ) {
+	echo bbp_get_forum_title( $forum_id );
+}
+	/**
+	 * Return the title of the forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Title of forum
+	 */
+	function bbp_get_forum_title( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$title    = get_the_title( $forum_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_title', $title, $forum_id );
+	}
+
+/**
+ * Output the forum archive title
+ *
+ * @since 2.0.0 bbPress (r3249)
+ *
+ * @param string $title Default text to use as title
+ */
+function bbp_forum_archive_title( $title = '' ) {
+	echo bbp_get_forum_archive_title( $title );
+}
+	/**
+	 * Return the forum archive title
+	 *
+	 * @since 2.0.0 bbPress (r3249)
+	 *
+	 * @param string $title Default text to use as title
+	 *
+	 * @return string The forum archive title
+	 */
+	function bbp_get_forum_archive_title( $title = '' ) {
+
+		// If no title was passed
+		if ( empty( $title ) ) {
+
+			// Set root text to page title
+			$page = bbp_get_page_by_path( bbp_get_root_slug() );
+			if ( ! empty( $page ) ) {
+				$title = get_the_title( $page->ID );
+
+			// Default to forum post type name label
+			} else {
+				$fto    = get_post_type_object( bbp_get_forum_post_type() );
+				$title  = $fto->labels->name;
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_archive_title', $title );
+	}
+
+/**
+ * Output the content of the forum
+ *
+ * @since 2.0.0 bbPress (r2780)
+ *
+ * @param int $forum_id Optional. Topic id
+ */
+function bbp_forum_content( $forum_id = 0 ) {
+	echo bbp_get_forum_content( $forum_id );
+}
+	/**
+	 * Return the content of the forum
+	 *
+	 * @since 2.0.0 bbPress (r2780)
+	 *
+	 * @param int $forum_id Optional. Topic id
+	 *
+	 * @return string Content of the forum
+	 */
+	function bbp_get_forum_content( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Check if password is required
+		if ( post_password_required( $forum_id ) ) {
+			return get_the_password_form();
+		}
+
+		$content = get_post_field( 'post_content', $forum_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_content', $content, $forum_id );
+	}
+
+/**
+ * Allow forum rows to have administrative actions
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @todo Links and filter
+ */
+function bbp_forum_row_actions() {
+	do_action( 'bbp_forum_row_actions' );
+}
+
+/**
+ * Output the forums last active ID
+ *
+ * @since 2.0.0 bbPress (r2860)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_active_id( $forum_id = 0 ) {
+	echo bbp_get_forum_last_active_id( $forum_id );
+}
+	/**
+	 * Return the forums last active ID
+	 *
+	 * @since 2.0.0 bbPress (r2860)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 *                        the last active id and forum id
+	 * @return int Forum's last active id
+	 */
+	function bbp_get_forum_last_active_id( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$active_id = (int) get_post_meta( $forum_id, '_bbp_last_active_id', true );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_last_active_id', $active_id, $forum_id );
+	}
+
+/**
+ * Output the forums last update date/time (aka freshness)
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_active_time( $forum_id = 0 ) {
+	echo bbp_get_forum_last_active_time( $forum_id );
+}
+	/**
+	 * Return the forums last update date/time (aka freshness)
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Forum last update date/time (freshness)
+	 */
+	function bbp_get_forum_last_active_time( $forum_id = 0 ) {
+
+		// Verify forum and get last active meta
+		$forum_id    = bbp_get_forum_id( $forum_id );
+		$last_active = get_post_meta( $forum_id, '_bbp_last_active_time', true );
+
+		if ( empty( $last_active ) ) {
+			$reply_id = bbp_get_forum_last_reply_id( $forum_id );
+			if ( ! empty( $reply_id ) ) {
+				$last_active = get_post_field( 'post_date', $reply_id );
+			} else {
+				$topic_id = bbp_get_forum_last_topic_id( $forum_id );
+				if ( ! empty( $topic_id ) ) {
+					$last_active = bbp_get_topic_last_active_time( $topic_id );
+				}
+			}
+		}
+
+		$active_time = ! empty( $last_active ) ? bbp_get_time_since( bbp_convert_date( $last_active ) ) : '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_active', $active_time, $forum_id );
+	}
+
+/**
+ * Output link to the most recent activity inside a forum.
+ *
+ * Outputs a complete link with attributes and content.
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_freshness_link( $forum_id = 0) {
+	echo bbp_get_forum_freshness_link( $forum_id );
+}
+	/**
+	 * Returns link to the most recent activity inside a forum.
+	 *
+	 * Returns a complete link with attributes and content.
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 */
+	function bbp_get_forum_freshness_link( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$active_id = bbp_get_forum_last_active_id( $forum_id );
+		$link_url  = $title = '';
+
+		if ( empty( $active_id ) ) {
+			$active_id = bbp_get_forum_last_reply_id( $forum_id );
+		}
+
+		if ( empty( $active_id ) ) {
+			$active_id = bbp_get_forum_last_topic_id( $forum_id );
+		}
+
+		if ( bbp_is_topic( $active_id ) ) {
+			$link_url = bbp_get_forum_last_topic_permalink( $forum_id );
+			$title    = bbp_get_forum_last_topic_title( $forum_id );
+		} elseif ( bbp_is_reply( $active_id ) ) {
+			$link_url = bbp_get_forum_last_reply_url( $forum_id );
+			$title    = bbp_get_forum_last_reply_title( $forum_id );
+		}
+
+		$time_since = bbp_get_forum_last_active_time( $forum_id );
+
+		if ( ! empty( $time_since ) && ! empty( $link_url ) ) {
+			$anchor = '<a href="' . esc_url( $link_url ) . '" title="' . esc_attr( $title ) . '">' . esc_html( $time_since ) . '</a>';
+		} else {
+			$anchor = esc_html__( 'No Topics', 'bbpress' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_freshness_link', $anchor, $forum_id, $time_since, $link_url, $title, $active_id );
+	}
+
+/**
+ * Output parent ID of a forum, if exists
+ *
+ * @since 2.1.0 bbPress (r3675)
+ *
+ * @param int $forum_id Forum ID
+ */
+function bbp_forum_parent_id( $forum_id = 0 ) {
+	echo bbp_get_forum_parent_id( $forum_id );
+}
+	/**
+	 * Return ID of forum parent, if exists
+	 *
+	 * @since 2.1.0 bbPress (r3675)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return int Forum parent
+	 */
+	function bbp_get_forum_parent_id( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$parent_id = (int) get_post_field( 'post_parent', $forum_id );
+
+		// Meta-data fallback
+		if ( empty( $parent_id ) ) {
+			$parent_id = (int) get_post_meta( $forum_id, '_bbp_forum_id', true );
+		}
+
+		// Filter
+		if ( ! empty( $parent_id ) ) {
+			$parent_id = (int) bbp_get_forum_id( $parent_id );
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_parent_id', $parent_id, $forum_id );
+	}
+
+/**
+ * Return array of parent forums
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return array Forum ancestors
+ */
+function bbp_get_forum_ancestors( $forum_id = 0 ) {
+	$forum_id  = bbp_get_forum_id( $forum_id );
+	$ancestors = array();
+	$forum     = bbp_get_forum( $forum_id );
+
+	if ( ! empty( $forum ) ) {
+		while ( 0 !== (int) $forum->post_parent ) {
+			$ancestors[] = $forum->post_parent;
+			$forum       = bbp_get_forum( $forum->post_parent );
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_forum_ancestors', $ancestors, $forum_id );
+}
+
+/**
+ * Return subforums of given forum
+ *
+ * @since 2.0.0 bbPress (r2747)
+ *
+ * @param array $args All the arguments supported by {@link WP_Query}
+ * @return mixed false if none, array of subs if yes
+ */
+function bbp_forum_get_subforums( $args = array() ) {
+
+	// Default return value
+	$retval = array();
+
+	// Use passed integer as post_parent
+	if ( is_numeric( $args ) && ! empty( $args ) ) {
+		$args = array( 'post_parent' => bbp_get_forum_id( $args ) );
+	}
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'post_parent'         => 0,
+		'post_type'           => bbp_get_forum_post_type(),
+		'posts_per_page'      => get_option( '_bbp_forums_per_page', 50 ),
+		'orderby'             => 'menu_order title',
+		'order'               => 'ASC',
+		'ignore_sticky_posts' => true,
+		'no_found_rows'       => true
+	), 'forum_get_subforums' );
+
+	// Ensure post_parent is properly set
+	$r['post_parent'] = bbp_get_forum_id( $r['post_parent'] );
+
+	// Query if post_parent has subforums
+	if ( ! empty( $r['post_parent'] ) && bbp_get_forum_subforum_count( $r['post_parent'], true ) ) {
+		$get_posts = new WP_Query();
+		$retval    = $get_posts->query( $r );
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_forum_get_subforums', $retval, $r, $args );
+}
+
+/**
+ * Output a list of forums (can be used to list subforums)
+ *
+ * @since 2.0.0 bbPress (r2708)
+ *
+ * @param array $args The function supports these args:
+ *  - before: To put before the output. Defaults to '<ul class="bbp-forums-list">'
+ *  - after: To put after the output. Defaults to '</ul>'
+ *  - link_before: To put before every link. Defaults to '<li class="bbp-forum">'
+ *  - link_after: To put after every link. Defaults to '</li>'
+ *  - sep: Separator. Defaults to ''. Make sure your markup is valid!
+ *  - count_before: String before each count Defaults to ' ('
+ *  - count_after: String before each count Defaults to ')'
+ *  - count_sep: Count separator. Defaults to ', '
+ *  - forum_id: Forum id. Defaults to ''
+ *  - show_topic_count - To show forum topic count or not. Defaults to true
+ *  - show_reply_count - To show forum reply count or not. Defaults to true
+ */
+function bbp_list_forums( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'before'           => '<ul class="bbp-forums-list">',
+		'after'            => '</ul>',
+		'link_before'      => '<li class="bbp-forum css-sep">',
+		'link_after'       => '</li>',
+		'sep'              => '',
+		'count_before'     => ' (',
+		'count_after'      => ')',
+		'count_sep'        => ', ',
+		'forum_id'         => bbp_get_forum_id(),
+		'show_topic_count' => true,
+		'show_reply_count' => true,
+		'echo'             => true,
+
+		// Retired, use 'sep' instead
+		'separator'        => false
+	), 'list_forums' );
+
+	/**
+	 * Necessary for backwards compatibility
+	 * @see https://bbpress.trac.wordpress.org/ticket/2900
+	 */
+	if ( ! empty( $r['separator'] ) ) {
+		$r['sep'] = $r['separator'];
+	}
+
+	// Default values
+	$links  = array();
+	$output = '';
+
+	// Query for subforums
+	$sub_forums = ! empty( $r['forum_id'] )
+		? bbp_forum_get_subforums( $r['forum_id'] )
+		: array();
+
+	// Loop through forums and create a list
+	if ( ! empty( $sub_forums ) ) {
+		foreach ( $sub_forums as $sub_forum ) {
+
+			// Get forum details
+			$count     = array();
+			$permalink = bbp_get_forum_permalink( $sub_forum->ID );
+			$title     = bbp_get_forum_title( $sub_forum->ID );
+
+			// Show topic count
+			if ( ! empty( $r['show_topic_count'] ) && ! bbp_is_forum_category( $sub_forum->ID ) ) {
+				$count['topic'] = bbp_get_forum_topic_count( $sub_forum->ID );
+			}
+
+			// Show reply count
+			if ( ! empty( $r['show_reply_count'] ) && ! bbp_is_forum_category( $sub_forum->ID ) ) {
+				$count['reply'] = bbp_get_forum_reply_count( $sub_forum->ID );
+			}
+
+			// Counts to show
+			$counts = ! empty( $count )
+				? $r['count_before'] . implode( $r['count_sep'], $count ) . $r['count_after']
+				: '';
+
+			// Subforum classes
+			$subforum_classes      = array( 'bbp-forum-link' );
+			$subforum_classes      = apply_filters( 'bbp_list_forums_subforum_classes', $subforum_classes, $sub_forum->ID );
+
+			// This could use bbp_get_forum_class() eventually...
+			$subforum_classes_attr = 'class="' . implode( ' ', array_map( 'sanitize_html_class', $subforum_classes ) ) . '"';
+
+			// Build this sub forums link
+			$links[] = $r['link_before'] . '<a href="' . esc_url( $permalink ) . '" ' . $subforum_classes_attr . '>' . $title . $counts . '</a>' . $r['link_after'];
+		}
+
+		// Maybe wrap output
+		$output = ! empty( $links )
+			? $r['before'] . implode( $r['sep'], $links ) . $r['after']
+			: '';
+	}
+
+	// Filter output
+	$the_list = apply_filters( 'bbp_list_forums', $output, $r, $args );
+
+	// Echo or return the forums list
+	if ( ! empty( $r['echo'] ) ) {
+		echo $the_list;
+	} else {
+		return $the_list;
+	}
+}
+
+/** Forum Subscriptions *******************************************************/
+
+/**
+ * Output the forum subscription link
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+ */
+function bbp_forum_subscription_link( $args = array() ) {
+	echo bbp_get_forum_subscription_link( $args );
+}
+
+	/**
+	 * Get the forum subscription link
+	 *
+	 * A custom wrapper for bbp_get_user_subscribe_link()
+	 *
+	 * @since 2.5.0 bbPress (r5156)
+	 * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+	 */
+	function bbp_get_forum_subscription_link( $args = array() ) {
+
+		// Defaults
+		$retval      = false;
+		$redirect_to = bbp_is_subscriptions()
+			? bbp_get_subscriptions_permalink()
+			: '';
+
+		// Parse the arguments
+		$r = bbp_parse_args( $args, array(
+			'user_id'     => bbp_get_current_user_id(),
+			'object_id'   => bbp_get_forum_id(),
+			'object_type' => 'post',
+			'before'      => '',
+			'after'       => '',
+			'subscribe'   => esc_html__( 'Subscribe',   'bbpress' ),
+			'unsubscribe' => esc_html__( 'Unsubscribe', 'bbpress' ),
+			'redirect_to' => $redirect_to
+		), 'get_forum_subscribe_link' );
+
+		// No link for categories until we support subscription hierarchy
+		// @see https://bbpress.trac.wordpress.org/ticket/2475
+		if ( ! bbp_is_forum_category() ) {
+			$retval = bbp_get_user_subscribe_link( $r );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_subscribe_link', $retval, $r, $args );
+	}
+
+/** Forum Last Topic **********************************************************/
+
+/**
+ * Output the forum's last topic id
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_topic_id( $forum_id = 0 ) {
+	echo bbp_get_forum_last_topic_id( $forum_id );
+}
+	/**
+	 * Return the forum's last topic id
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return int Forum's last topic id
+	 */
+	function bbp_get_forum_last_topic_id( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$topic_id = (int) get_post_meta( $forum_id, '_bbp_last_topic_id', true );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_last_topic_id', $topic_id, $forum_id );
+	}
+
+/**
+ * Output the title of the last topic inside a forum
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_topic_title( $forum_id = 0 ) {
+	echo bbp_get_forum_last_topic_title( $forum_id );
+}
+	/**
+	 * Return the title of the last topic inside a forum
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Forum's last topic's title
+	 */
+	function bbp_get_forum_last_topic_title( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$topic_id = bbp_get_forum_last_topic_id( $forum_id );
+		$title    = ! empty( $topic_id ) ? bbp_get_topic_title( $topic_id ) : '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_topic_title', $title, $forum_id );
+	}
+
+/**
+ * Output the link to the last topic in a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_topic_permalink( $forum_id = 0 ) {
+	echo esc_url( bbp_get_forum_last_topic_permalink( $forum_id ) );
+}
+	/**
+	 * Return the link to the last topic in a forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Permanent link to topic
+	 */
+	function bbp_get_forum_last_topic_permalink( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$topic_id = bbp_get_forum_last_topic_id( $forum_id );
+		$link     = bbp_get_topic_permalink( $topic_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_topic_permalink', $link, $forum_id, $topic_id );
+	}
+
+/**
+ * Return the author ID of the last topic of a forum
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return int Forum's last topic's author id
+ */
+function bbp_get_forum_last_topic_author_id( $forum_id = 0 ) {
+	$forum_id  = bbp_get_forum_id( $forum_id );
+	$topic_id  = bbp_get_forum_last_topic_id( $forum_id );
+	$author_id = bbp_get_topic_author_id( $topic_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_forum_last_topic_author_id', (int) $author_id, $forum_id, $topic_id );
+}
+
+/**
+ * Output link to author of last topic of forum
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_topic_author_link( $forum_id = 0 ) {
+	echo bbp_get_forum_last_topic_author_link( $forum_id );
+}
+	/**
+	 * Return link to author of last topic of forum
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Forum's last topic's author link
+	 */
+	function bbp_get_forum_last_topic_author_link( $forum_id = 0 ) {
+		$forum_id    = bbp_get_forum_id( $forum_id );
+		$author_id   = bbp_get_forum_last_topic_author_id( $forum_id );
+		$author_link = bbp_get_user_profile_link( $author_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_topic_author_link', $author_link, $forum_id );
+	}
+
+/** Forum Last Reply **********************************************************/
+
+/**
+ * Output the forums last reply id
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_id( $forum_id = 0 ) {
+	echo bbp_get_forum_last_reply_id( $forum_id );
+}
+	/**
+	 * Return the forums last reply id
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return int Forum's last reply id
+	 */
+	function bbp_get_forum_last_reply_id( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$reply_id = (int) get_post_meta( $forum_id, '_bbp_last_reply_id', true );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_last_reply_id', $reply_id, $forum_id );
+	}
+
+/**
+ * Output the title of the last reply inside a forum
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_title( $forum_id = 0 ) {
+	echo bbp_get_forum_last_reply_title( $forum_id );
+}
+	/**
+	 * Return the title of the last reply inside a forum
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string
+	 */
+	function bbp_get_forum_last_reply_title( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$reply_id = bbp_get_forum_last_reply_id( $forum_id );
+		$title    = bbp_get_reply_title( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_reply_title', $title, $forum_id, $reply_id );
+	}
+
+/**
+ * Output the link to the last reply in a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_permalink( $forum_id = 0 ) {
+	echo esc_url( bbp_get_forum_last_reply_permalink( $forum_id ) );
+}
+	/**
+	 * Return the link to the last reply in a forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 *
+	 * @return string Permanent link to the forum's last reply
+	 */
+	function bbp_get_forum_last_reply_permalink( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$reply_id = bbp_get_forum_last_reply_id( $forum_id );
+		$link     = bbp_get_reply_permalink( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_reply_permalink', $link, $forum_id, $reply_id );
+	}
+
+/**
+ * Output the url to the last reply in a forum
+ *
+ * @since 2.0.0 bbPress (r2683)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_url( $forum_id = 0 ) {
+	echo esc_url( bbp_get_forum_last_reply_url( $forum_id ) );
+}
+	/**
+	 * Return the url to the last reply in a forum
+	 *
+	 * @since 2.0.0 bbPress (r2683)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Paginated URL to latest reply
+	 */
+	function bbp_get_forum_last_reply_url( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// If forum has replies, get the last reply and use its url
+		$reply_id = bbp_get_forum_last_reply_id( $forum_id );
+		if ( ! empty( $reply_id ) ) {
+			$reply_url = bbp_get_reply_url( $reply_id );
+
+		// No replies, so look for topics and use last permalink
+		} else {
+			$reply_url = bbp_get_forum_last_topic_permalink( $forum_id );
+
+			// No topics either, so set $reply_url as empty string
+			if ( empty( $reply_url ) ) {
+				$reply_url = '';
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_reply_url', $reply_url, $forum_id, $reply_id );
+	}
+
+/**
+ * Output author ID of last reply of forum
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_author_id( $forum_id = 0 ) {
+	echo bbp_get_forum_last_reply_author_id( $forum_id );
+}
+	/**
+	 * Return author ID of last reply of forum
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return int Forum's last reply author id
+	 */
+	function bbp_get_forum_last_reply_author_id( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$reply_id  = bbp_get_forum_last_reply_id( $forum_id );
+		$author_id = bbp_get_reply_author_id( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_reply_author_id', $author_id, $forum_id, $reply_id );
+	}
+
+/**
+ * Output link to author of last reply of forum
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_last_reply_author_link( $forum_id = 0 ) {
+	echo bbp_get_forum_last_reply_author_link( $forum_id );
+}
+	/**
+	 * Return link to author of last reply of forum
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Link to author of last reply of forum
+	 */
+	function bbp_get_forum_last_reply_author_link( $forum_id = 0 ) {
+		$forum_id    = bbp_get_forum_id( $forum_id );
+		$author_id   = bbp_get_forum_last_reply_author_id( $forum_id );
+		$author_link = bbp_get_user_profile_link( $author_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_last_reply_author_link', $author_link, $forum_id, $author_id );
+	}
+
+/** Forum Counts **************************************************************/
+
+/**
+ * Output the topics link of the forum
+ *
+ * @since 2.0.0 bbPress (r2883)
+ *
+ * @param int $forum_id Optional. Topic id
+ */
+function bbp_forum_topics_link( $forum_id = 0 ) {
+	echo bbp_get_forum_topics_link( $forum_id );
+}
+
+	/**
+	 * Return the topics link of the forum
+	 *
+	 * @since 2.0.0 bbPress (r2883)
+	 *
+	 * @param int $forum_id Optional. Topic id
+	 */
+	function bbp_get_forum_topics_link( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$link     = bbp_get_forum_permalink( $forum_id );
+		$topics   = sprintf( _n( '%s topic', '%s topics', bbp_get_forum_topic_count( $forum_id, true, true ), 'bbpress' ), bbp_get_forum_topic_count( $forum_id, true, false ) );
+
+		// First link never has view=all
+		$retval = bbp_get_view_all( 'edit_others_topics' )
+			? "<a href='" . esc_url( bbp_remove_view_all( $link ) ) . "'>" . esc_html( $topics ) . "</a>"
+			: esc_html( $topics );
+
+		// Get deleted topics
+		$deleted_int = bbp_get_forum_topic_count_hidden( $forum_id, false, true );
+
+		// This forum has hidden topics
+		if ( ! empty( $deleted_int ) && current_user_can( 'edit_others_topics' ) ) {
+
+			// Hidden text
+			$deleted_num = bbp_get_forum_topic_count_hidden( $forum_id, false, false );
+			$extra       = ' ' . sprintf( _n( '(+%s hidden)', '(+%s hidden)', $deleted_int, 'bbpress' ), $deleted_num );
+
+			// Hidden link
+			$retval .= ! bbp_get_view_all( 'edit_others_topics' )
+				? " <a href='" . esc_url( bbp_add_view_all( $link, true ) ) . "'>" . esc_html( $extra ) . "</a>"
+				: " {$extra}";
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_topics_link', $retval, $forum_id );
+	}
+
+/**
+ * Output total sub-forum count of a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id to check
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_subforum_count( $forum_id = 0, $integer = false ) {
+	echo bbp_get_forum_subforum_count( $forum_id, $integer );
+}
+	/**
+	 * Return total subforum count of a forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Forum's subforum count
+	 */
+	function bbp_get_forum_subforum_count( $forum_id = 0, $integer = false ) {
+		$forum_id    = bbp_get_forum_id( $forum_id );
+		$forum_count = (int) get_post_meta( $forum_id, '_bbp_forum_subforum_count', true );
+		$filter      = ( true === $integer )
+			? 'bbp_get_forum_subforum_count_int'
+			: 'bbp_get_forum_subforum_count';
+
+		return apply_filters( $filter, $forum_count, $forum_id );
+	}
+
+/**
+ * Output total topic count of a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $total_count Optional. To get the total count or normal count?
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_topic_count( $forum_id = 0, $total_count = true, $integer = false ) {
+	echo bbp_get_forum_topic_count( $forum_id, $total_count, $integer );
+}
+	/**
+	 * Return total topic count of a forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param bool $total_count Optional. To get the total count or normal
+	 *                           count? Defaults to total.
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Forum topic count
+	 */
+	function bbp_get_forum_topic_count( $forum_id = 0, $total_count = true, $integer = false ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$meta_key = empty( $total_count ) ? '_bbp_topic_count' : '_bbp_total_topic_count';
+		$topics   = (int) get_post_meta( $forum_id, $meta_key, true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_forum_topic_count_int'
+			: 'bbp_get_forum_topic_count';
+
+		return apply_filters( $filter, $topics, $forum_id );
+	}
+
+/**
+ * Output total reply count of a forum
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $total_count Optional. To get the total count or normal count?
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_reply_count( $forum_id = 0, $total_count = true, $integer = false ) {
+	echo bbp_get_forum_reply_count( $forum_id, $total_count, $integer );
+}
+	/**
+	 * Return total post count of a forum
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param bool $total_count Optional. To get the total count or normal
+	 *                           count?
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Forum reply count
+	 */
+	function bbp_get_forum_reply_count( $forum_id = 0, $total_count = true, $integer = false ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$meta_key = empty( $total_count ) ? '_bbp_reply_count' : '_bbp_total_reply_count';
+		$replies  = (int) get_post_meta( $forum_id, $meta_key, true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_forum_reply_count_int'
+			: 'bbp_get_forum_reply_count';
+
+		return apply_filters( $filter, $replies, $forum_id );
+	}
+
+/**
+ * Output total post count of a forum
+ *
+ * @since 2.0.0 bbPress (r2954)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $total_count Optional. To get the total count or normal count?
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_post_count( $forum_id = 0, $total_count = true, $integer = false ) {
+	echo bbp_get_forum_post_count( $forum_id, $total_count, $integer );
+}
+	/**
+	 * Return total post count of a forum
+	 *
+	 * @since 2.0.0 bbPress (r2954)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param bool $total_count Optional. To get the total count or normal
+	 *                           count?
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Forum post count
+	 */
+	function bbp_get_forum_post_count( $forum_id = 0, $total_count = true, $integer = false ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$topics   = bbp_get_forum_topic_count( $forum_id, $total_count, true );
+		$replies  = bbp_get_forum_reply_count( $forum_id, $total_count, true );
+		$retval   = ( $replies + $topics );
+		$filter   = ( true === $integer )
+			? 'bbp_get_forum_post_count_int'
+			: 'bbp_get_forum_post_count';
+
+		return apply_filters( $filter, $retval, $forum_id );
+	}
+
+/**
+ * Output total hidden topic count of a forum (hidden includes trashed, spammed,
+ * and pending topics)
+ *
+ * @since 2.0.0 bbPress (r2883)
+ * @since 2.6.0 bbPress (r6922) Changed function signature to add total counts
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $total_count Optional. To get the total count or normal count?
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_topic_count_hidden( $forum_id = 0, $total_count = true, $integer = null ) {
+	echo bbp_get_forum_topic_count_hidden( $forum_id, $total_count, $integer );
+}
+	/**
+	 * Return total hidden topic count of a forum (hidden includes trashed,
+	 * spammed and pending topics)
+	 *
+	 * @since 2.0.0 bbPress (r2883)
+	 * @since 2.6.0 bbPress (r6922) Changed function signature to add total counts
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param bool $total_count Optional. To get the total count or normal count?
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Topic hidden topic count
+	 */
+	function bbp_get_forum_topic_count_hidden( $forum_id = 0, $total_count = true, $integer = null ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$meta_key = empty( $total_count ) ? '_bbp_topic_count_hidden' : '_bbp_topic_reply_count_hidden';
+		$topics   = (int) get_post_meta( $forum_id, $meta_key, true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_forum_topic_count_hidden_int'
+			: 'bbp_get_forum_topic_count_hidden';
+
+		return apply_filters( $filter, $topics, $forum_id );
+	}
+
+/**
+ * Output total hidden reply count of a forum (hidden includes trashed, spammed,
+ * and pending replies)
+ *
+ * @since 2.6.0 bbPress (r6922)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $total_count Optional. To get the total count or normal count?
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_forum_reply_count_hidden( $forum_id = 0, $total_count = true, $integer = false ) {
+	echo bbp_get_forum_reply_count_hidden( $forum_id, $total_count, $integer );
+}
+	/**
+	 * Return total hidden reply count of a forum (hidden includes trashed,
+	 * spammed and pending replies)
+	 *
+	 * @since 2.6.0 bbPress (r6922)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @param bool $total_count Optional. To get the total count or normal
+	 *                           count?
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Forum reply count
+	 */
+	function bbp_get_forum_reply_count_hidden( $forum_id = 0, $total_count = true, $integer = false ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$meta_key = empty( $total_count ) ? '_bbp_reply_count_hidden' : '_bbp_total_reply_count_hidden';
+		$replies  = (int) get_post_meta( $forum_id, $meta_key, true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_forum_reply_count_hidden_int'
+			: 'bbp_get_forum_reply_count_hidden';
+
+		return apply_filters( $filter, $replies, $forum_id );
+	}
+
+/**
+ * Output the status of the forum
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_status( $forum_id = 0 ) {
+	echo bbp_get_forum_status( $forum_id );
+}
+	/**
+	 * Return the status of the forum
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Status of forum
+	 */
+	function bbp_get_forum_status( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$status   = get_post_meta( $forum_id, '_bbp_status', true );
+
+		if ( empty( $status ) ) {
+			$status = 'open';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_status', $status, $forum_id );
+	}
+
+/**
+ * Output the visibility of the forum
+ *
+ * @since 2.0.0 bbPress (r2997)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_visibility( $forum_id = 0 ) {
+	echo bbp_get_forum_visibility( $forum_id );
+}
+	/**
+	 * Return the visibility of the forum
+	 *
+	 * @since 2.0.0 bbPress (r2997)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Status of forum
+	 */
+	function bbp_get_forum_visibility( $forum_id = 0 ) {
+		$forum_id   = bbp_get_forum_id( $forum_id );
+		$visibility = get_post_status( $forum_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_visibility', $visibility, $forum_id );
+	}
+
+/**
+ * Output the type of the forum
+ *
+ * @since 2.1.0 bbPress (r3563)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_type( $forum_id = 0 ) {
+	echo bbp_get_forum_type( $forum_id );
+}
+	/**
+	 * Return the type of forum (category/forum/etc...)
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return bool Whether the forum is a category or not
+	 */
+	function bbp_get_forum_type( $forum_id = 0 ) {
+		$forum_id = bbp_get_forum_id( $forum_id );
+		$retval   = get_post_meta( $forum_id, '_bbp_forum_type', true );
+
+		if ( empty( $retval ) ) {
+			$retval = 'forum';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_type', $retval, $forum_id );
+	}
+
+/**
+ * Is the forum a category?
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @return bool Whether the forum is a category or not
+ */
+function bbp_is_forum_category( $forum_id = 0 ) {
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$type     = bbp_get_forum_type( $forum_id );
+	$retval   = ( ! empty( $type ) && 'category' === $type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_category', (bool) $retval, $forum_id );
+}
+
+/**
+ * Is the forum open?
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $check_ancestors Check if the ancestors are open (only
+ *                               if they're a category)
+ * @return bool Whether the forum is open or not
+ */
+function bbp_is_forum_open( $forum_id = 0, $check_ancestors = true ) {
+	return ! bbp_is_forum_closed( $forum_id, $check_ancestors );
+}
+
+/**
+* Is the forum closed?
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $check_ancestors Check if the ancestors are closed (only
+ *                               if they're a category)
+ * @return bool True if closed, false if not
+ */
+function bbp_is_forum_closed( $forum_id = 0, $check_ancestors = true ) {
+
+	// Get the forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Check if the forum or one of it's ancestors is closed
+	$retval   = bbp_is_forum_status( $forum_id, bbp_get_closed_status_id(), $check_ancestors, 'OR' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_closed', (bool) $retval, $forum_id, $check_ancestors );
+}
+
+/**
+ * Check if the forum status is a specific one, also maybe checking ancestors
+ *
+ * @since 2.6.0 bbPress (r5499)
+ *
+ * @param bool $status_name The forum status name to check
+ * @param bool $check_ancestors Check the forum ancestors
+ * @param string $operator The logical operation to perform.
+ *      'OR' means only one forum from the tree needs to match;
+ *      'AND' means all forums must match. The default is 'AND'.
+ * @return bool True if match, false if not
+ */
+function bbp_is_forum_status( $forum_id, $status_name, $check_ancestors = true, $operator = 'AND' ) {
+
+	// Setup some default variables
+	$count        = 0;
+	$retval       = false;
+	$operator     = strtoupper( $operator );
+	$forum_id     = bbp_get_forum_id( $forum_id );
+	$forum_status = bbp_get_forum_status( $forum_id );
+
+	// Quickly compare statuses of first forum ID
+	if ( $status_name === $forum_status ) {
+		$retval = true;
+		$count++;
+	}
+
+	// Let's check the forum's ancestors too
+	if ( ! empty( $check_ancestors ) ) {
+
+		// Adjust the ancestor check based on the count
+		switch( $operator ) {
+			default:
+			case 'AND':
+				$check_ancestors = ( $count > 0 );
+				break;
+
+			case 'OR':
+				$check_ancestors = ( $count < 1 );
+				break;
+		}
+
+		// Ancestor check passed, so continue looping through them
+		if ( ! empty( $check_ancestors ) ) {
+
+			// Loop through the forum ancestors
+			foreach ( (array) bbp_get_forum_ancestors( $forum_id ) as $ancestor ) {
+
+				// Check if the forum is a category
+				if ( bbp_is_forum_category( $ancestor ) ) {
+
+					// Check the ancestor forum status
+					$retval = bbp_is_forum_status( $ancestor, $status_name, false );
+					if ( true === $retval ) {
+						$count++;
+					}
+				}
+
+				// Break when it reach the max count
+				if ( ( $operator === 'OR' ) && ( $count >= 1 ) ) {
+					break;
+				}
+			}
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_status', $retval, $count, $forum_id, $status_name, $check_ancestors, $operator );
+}
+
+/**
+ * Is the forum public?
+ *
+ * @since 2.0.0 bbPress (r2997)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $check_ancestors Check if the ancestors are public
+ * @return bool True if closed, false if not
+ */
+function bbp_is_forum_public( $forum_id = 0, $check_ancestors = true ) {
+
+	// Get the forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Check if the forum and all of it's ancestors are public
+	$retval   = bbp_is_forum_visibility( $forum_id, bbp_get_public_status_id(), $check_ancestors );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_public', $retval, $forum_id, $check_ancestors );
+}
+
+/**
+ * Is the forum private?
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $check_ancestors Check if the ancestors are private
+ * @return bool True if private, false if not
+ */
+function bbp_is_forum_private( $forum_id = 0, $check_ancestors = true ) {
+
+	// Get the forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Check if the forum or one of it's ancestors is private
+	$retval   = bbp_is_forum_visibility( $forum_id, bbp_get_private_status_id(), $check_ancestors, 'OR' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_private', $retval, $forum_id, $check_ancestors );
+}
+
+/**
+ * Is the forum hidden?
+ *
+ * @since 2.0.0 bbPress (r2997)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $check_ancestors Check if the ancestors are private (only if
+ *                               they're a category)
+ * @return bool True if hidden, false if not
+ */
+function bbp_is_forum_hidden( $forum_id = 0, $check_ancestors = true ) {
+
+	// Get the forum ID
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Check if the forum or one of it's ancestors is hidden
+	$retval   = bbp_is_forum_visibility( $forum_id, bbp_get_hidden_status_id(), $check_ancestors, 'OR' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_hidden', $retval, $forum_id, $check_ancestors );
+}
+
+/**
+ * Check the forum visibility ID
+ *
+ * @since 2.6.0 bbPress (r5499)
+ *
+ * @param int $forum_id Optional. Forum id
+ * @param bool $status_name The post status name to check
+ * @param bool $check_ancestors Check the forum ancestors
+ * @param string $operator The logical operation to perform.
+ *      'OR' means only one forum from the tree needs to match;
+ *      'AND' means all forums must match. The default is 'AND'.
+ * @return bool True if match, false if not
+ */
+function bbp_is_forum_visibility( $forum_id, $status_name, $check_ancestors = true, $operator = 'AND' ) {
+
+	// Setup some default variables
+	$count      = 0;
+	$retval     = false;
+	$operator   = strtoupper( $operator );
+	$forum_id   = bbp_get_forum_id( $forum_id );
+	$visibility = bbp_get_forum_visibility( $forum_id );
+
+	// Quickly compare visibility of first forum ID
+	if ( $status_name === $visibility ) {
+		$retval = true;
+		$count++;
+	}
+
+	// Let's check the forum's ancestors too
+	if ( ! empty( $check_ancestors ) ) {
+
+		// Adjust the ancestor check based on the count
+		switch ( $operator ) {
+
+			// Adjust the ancestor check based on the count
+			default:
+			case 'AND':
+				$check_ancestors = ( $count > 0 );
+				break;
+
+			case 'OR':
+				$check_ancestors = ( $count < 1 );
+				break;
+		}
+
+		// Ancestor check passed, so continue looping through them
+		if ( ! empty( $check_ancestors ) ) {
+
+			// Loop through the forum ancestors
+			foreach ( (array) bbp_get_forum_ancestors( $forum_id ) as $ancestor ) {
+
+				// Check if the forum is not a category
+				if ( bbp_is_forum( $ancestor ) ) {
+
+					// Check the forum visibility
+					$retval = bbp_is_forum_visibility( $ancestor, $status_name, false );
+					if ( true === $retval ) {
+						$count++;
+					}
+				}
+
+				// Break when it reach the max count
+				if ( ( $operator === 'OR' ) && ( $count >= 1 ) ) {
+					break;
+				}
+			}
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_forum_visibility', $retval, $count, $forum_id, $status_name, $check_ancestors, $operator );
+}
+
+/**
+ * Output the author ID of the forum
+ *
+ * @since 2.1.0 bbPress (r3675)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_author_id( $forum_id = 0 ) {
+	echo bbp_get_forum_author_id( $forum_id );
+}
+	/**
+	 * Return the author ID of the forum
+	 *
+	 * @since 2.1.0 bbPress (r3675)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 *                        id and forum id
+	 * @return string Author of forum
+	 */
+	function bbp_get_forum_author_id( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$author_id = get_post_field( 'post_author', $forum_id );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_forum_author_id', (int) $author_id, $forum_id );
+	}
+
+/**
+ * Output the author of the forum
+ *
+ * @since 2.1.0 bbPress (r3675)
+ *
+ * @param int $forum_id Optional. Forum id
+ */
+function bbp_forum_author_display_name( $forum_id = 0 ) {
+	echo bbp_get_forum_author_display_name( $forum_id );
+}
+	/**
+	 * Return the author of the forum
+	 *
+	 * @since 2.1.0 bbPress (r3675)
+	 *
+	 * @param int $forum_id Optional. Forum id
+	 * @return string Author of forum
+	 */
+	function bbp_get_forum_author_display_name( $forum_id = 0 ) {
+		$forum_id  = bbp_get_forum_id( $forum_id );
+		$author_id = bbp_get_forum_author_id( $forum_id );
+		$author    = get_the_author_meta( 'display_name', $author_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_author_display_name', $author, $forum_id, $author_id );
+	}
+
+/**
+ * Replace forum meta details for users that cannot view them.
+ *
+ * @since 2.0.0 bbPress (r3162)
+ *
+ * @param string $retval
+ * @param int $forum_id
+ *
+ * @return string
+ */
+function bbp_suppress_private_forum_meta( $retval, $forum_id ) {
+	if ( bbp_is_forum_private( $forum_id, false ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+		$retval = '-';
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_suppress_private_forum_meta', $retval );
+}
+
+/**
+ * Replace forum author details for users that cannot view them.
+ *
+ * @since 2.0.0 bbPress (r3162)
+ *
+ * @param string $author_link
+ * @param array $args
+ *
+ * @return string
+ */
+function bbp_suppress_private_author_link( $author_link = '', $args = array() ) {
+
+	// Assume the author link is the return value
+	$retval = $author_link;
+
+	// Show the normal author link
+	if ( ! empty( $args['post_id'] ) && ! current_user_can( 'read_private_forums' ) ) {
+
+		// What post type are we looking at?
+		switch ( get_post_type( $args['post_id'] ) ) {
+
+			// Topic
+			case bbp_get_topic_post_type() :
+				$forum_id = bbp_get_topic_forum_id( $args['post_id'] );
+				break;
+
+			// Reply
+			case bbp_get_reply_post_type() :
+				$forum_id = bbp_get_reply_forum_id( $args['post_id'] );
+				break;
+
+			// Post
+			default :
+				$forum_id = bbp_get_forum_id( $args['post_id'] );
+				break;
+		}
+
+		// Hide if forum is private
+		if ( bbp_is_forum_private( $forum_id ) ) {
+			$retval = '';
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_suppress_private_author_link', $retval, $author_link, $args );
+}
+
+/**
+ * Output the row class of a forum
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $forum_id Optional. Forum ID.
+ * @param array Extra classes you can pass when calling this function
+ */
+function bbp_forum_class( $forum_id = 0, $classes = array() ) {
+	echo bbp_get_forum_class( $forum_id, $classes );
+}
+	/**
+	 * Return the row class of a forum
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $forum_id Optional. Forum ID
+	 * @param array Extra classes you can pass when calling this function
+	 * @return string Row class of the forum
+	 */
+	function bbp_get_forum_class( $forum_id = 0, $classes = array() ) {
+		$bbp        = bbpress();
+		$forum_id   = bbp_get_forum_id( $forum_id );
+		$parent_id  = bbp_get_forum_parent_id( $forum_id );
+		$author_id  = bbp_get_forum_author_id( $forum_id );
+		$status     = bbp_get_forum_status( $forum_id );
+		$visibility = bbp_get_forum_visibility( $forum_id );
+		$classes    = array_filter( (array) $classes );
+		$count      = isset( $bbp->forum_query->current_post )
+			? (int) $bbp->forum_query->current_post
+			: 1;
+
+		//  Stripes
+		$even_odd = ( $count % 2 )
+			? 'even'
+			: 'odd';
+
+		// User is moderator of forum
+		$forum_moderator = ( bbp_is_user_forum_moderator( $author_id, $forum_id ) === $author_id )
+			? 'forum-mod'
+			: '';
+
+		// Is forum a non-postable category?
+		$category = bbp_is_forum_category( $forum_id )
+			? 'status-category'
+			: '';
+
+		// Forum has children?
+		$subs = bbp_get_forum_subforum_count( $forum_id )
+			? 'bbp-has-subforums'
+			: '';
+
+		// Forum has parent?
+		$parent = ! empty( $parent_id )
+			? 'bbp-parent-forum-' . $parent_id
+			: '';
+
+		// Get forum classes
+		$forum_classes = array(
+			'loop-item-'            . $count,
+			'bbp-forum-status-'     . $status,
+			'bbp-forum-visibility-' . $visibility,
+			$even_odd,
+			$forum_moderator,
+			$category,
+			$subs,
+			$parent
+		);
+
+		// Run the topic classes through the post-class filters, which also
+		// handles the escaping of each individual class.
+		$post_classes = get_post_class( array_merge( $classes, $forum_classes ), $forum_id );
+
+		// Filter
+		$new_classes  = apply_filters( 'bbp_get_forum_class', $post_classes, $forum_id, $classes );
+
+		// Return
+		return 'class="' . implode( ' ', $new_classes ) . '"';
+	}
+
+/** Single Forum **************************************************************/
+
+/**
+ * Output a fancy description of the current forum, including total topics,
+ * total replies, and last activity.
+ *
+ * @since 2.0.0 bbPress (r2860)
+ *
+ * @param array $args Arguments passed to alter output
+ */
+function bbp_single_forum_description( $args = array() ) {
+	echo bbp_get_single_forum_description( $args );
+}
+	/**
+	 * Return a fancy description of the current forum, including total
+	 * topics, total replies, and last activity.
+	 *
+	 * @since 2.0.0 bbPress (r2860)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - forum_id: Forum id
+	 *  - before: Before the text
+	 *  - after: After the text
+	 *  - size: Size of the avatar
+	 * @return string Filtered forum description
+	 */
+	function bbp_get_single_forum_description( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'forum_id'  => 0,
+			'before'    => '<div class="bbp-template-notice info"><ul><li class="bbp-forum-description">',
+			'after'     => '</li></ul></div>',
+			'size'      => 14,
+			'feed'      => true
+		), 'get_single_forum_description' );
+
+		// Validate forum_id
+		$forum_id = bbp_get_forum_id( $r['forum_id'] );
+
+		// Unhook the 'view all' query var adder
+		remove_filter( 'bbp_get_forum_permalink', 'bbp_add_view_all' );
+
+		// Get some forum data
+		$tc_int      = bbp_get_forum_topic_count( $forum_id, true, true  );
+		$rc_int      = bbp_get_forum_reply_count( $forum_id, true, true  );
+		$topic_count = bbp_get_forum_topic_count( $forum_id, true, false );
+		$reply_count = bbp_get_forum_reply_count( $forum_id, true, false );
+		$last_active = bbp_get_forum_last_active_id( $forum_id );
+
+		// Has replies
+		if ( ! empty( $reply_count ) ) {
+			$reply_text = sprintf( _n( '%s reply', '%s replies', $rc_int, 'bbpress' ), $reply_count );
+		}
+
+		// Forum has active data
+		if ( ! empty( $last_active ) ) {
+			$topic_text      = bbp_get_forum_topics_link( $forum_id );
+			$time_since      = bbp_get_forum_freshness_link( $forum_id );
+			$last_updated_by = bbp_get_author_link( array( 'post_id' => $last_active, 'size' => $r['size'] ) );
+
+		// Forum has no last active data
+		} else {
+			$topic_text      = sprintf( _n( '%s topic', '%s topics', $tc_int, 'bbpress' ), $topic_count );
+		}
+
+		// Forum has active data
+		if ( ! empty( $last_active ) ) {
+
+			// Has replies
+			if ( ! empty( $reply_count ) ) {
+				$retstr = bbp_is_forum_category( $forum_id )
+					? sprintf( esc_html__( 'This category has %1$s, %2$s, and was last updated %3$s by %4$s.', 'bbpress' ), $topic_text, $reply_text, $time_since, $last_updated_by )
+					: sprintf( esc_html__( 'This forum has %1$s, %2$s, and was last updated %3$s by %4$s.',    'bbpress' ), $topic_text, $reply_text, $time_since, $last_updated_by );
+
+			// Only has topics
+			} else {
+				$retstr = bbp_is_forum_category( $forum_id )
+					? sprintf( esc_html__( 'This category has %1$s, and was last updated %2$s by %3$s.', 'bbpress' ), $topic_text, $time_since, $last_updated_by )
+					: sprintf( esc_html__( 'This forum has %1$s, and was last updated %2$s by %3$s.',    'bbpress' ), $topic_text, $time_since, $last_updated_by );
+			}
+
+		// Forum has no last active data (but does have topics & replies)
+		} elseif ( ! empty( $reply_count ) ) {
+			$retstr = bbp_is_forum_category( $forum_id )
+				? sprintf( esc_html__( 'This category has %1$s and %2$s.', 'bbpress' ), $topic_text, $reply_text )
+				: sprintf( esc_html__( 'This forum has %1$s and %2$s.',    'bbpress' ), $topic_text, $reply_text );
+
+		// Forum has no last active data or replies (but does have topics)
+		} elseif ( ! empty( $topic_count ) ) {
+			$retstr = bbp_is_forum_category( $forum_id )
+				? sprintf( esc_html__( 'This category has %1$s.', 'bbpress' ), $topic_text )
+				: sprintf( esc_html__( 'This forum has %1$s.',    'bbpress' ), $topic_text );
+
+		// Forum is empty
+		} else {
+			$retstr = esc_html__( 'This forum is empty.', 'bbpress' );
+		}
+
+		// Add the 'view all' filter back
+		add_filter( 'bbp_get_forum_permalink', 'bbp_add_view_all' );
+
+		// Combine the elements together
+		$retstr = $r['before'] . $retstr . $r['after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_single_forum_description', $retstr, $r, $args );
+	}
+
+/** Forms *********************************************************************/
+
+/**
+ * Output the value of forum title field
+ *
+ * @since 2.1.0 bbPress (r3551)
+ */
+function bbp_form_forum_title() {
+	echo bbp_get_form_forum_title();
+}
+	/**
+	 * Return the value of forum title field
+	 *
+	 * @since 2.1.0 bbPress (r3551)
+	 *
+	 * @return string Value of forum title field
+	 */
+	function bbp_get_form_forum_title() {
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_title'] ) ) {
+			$forum_title = wp_unslash( $_POST['bbp_forum_title'] );
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() ) {
+			$forum_title = bbp_get_global_post_field( 'post_title', 'raw' );
+
+		// No data
+		} else {
+			$forum_title = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_title', $forum_title );
+	}
+
+/**
+ * Output the value of forum content field
+ *
+ * @since 2.1.0 bbPress (r3551)
+ */
+function bbp_form_forum_content() {
+	echo bbp_get_form_forum_content();
+}
+	/**
+	 * Return the value of forum content field
+	 *
+	 * @since 2.1.0 bbPress (r3551)
+	 *
+	 * @return string Value of forum content field
+	 */
+	function bbp_get_form_forum_content() {
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_content'] ) ) {
+			$forum_content = wp_unslash( $_POST['bbp_forum_content'] );
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() ) {
+			$forum_content = bbp_get_global_post_field( 'post_content', 'raw' );
+
+		// No data
+		} else {
+			$forum_content = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_content', $forum_content );
+	}
+
+/**
+ * Output value of forum moderators field
+ *
+ * @since 2.6.0 bbPress (r5837)
+ */
+function bbp_form_forum_moderators() {
+	echo bbp_get_form_forum_moderators();
+}
+	/**
+	 * Return value of forum moderators field
+	 *
+	 * @since 2.6.0 bbPress (r5837)
+	 *
+	 * @return string Value of forum mods field
+	 */
+	function bbp_get_form_forum_moderators() {
+
+		// Default return value
+		$forum_mods = '';
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_moderators'] ) ) {
+			$forum_mods = wp_unslash( $_POST['bbp_moderators'] );
+
+		// Get edit data
+		} elseif ( bbp_is_single_forum() || bbp_is_forum_edit() ) {
+
+			// Get the forum ID
+			$forum_id = bbp_get_forum_id( get_the_ID() );
+
+			// Forum exists
+			if ( ! empty( $forum_id ) ) {
+
+				// Get moderator IDs
+				$user_ids = bbp_get_moderator_ids( $forum_id );
+				if ( ! empty( $user_ids ) ) {
+					$user_nicenames = bbp_get_user_nicenames_from_ids( $user_ids );
+
+					// Comma separate user nicenames
+					if ( ! empty( $user_nicenames ) ) {
+						$forum_mods = implode( ', ', wp_list_pluck( $user_nicenames, 'user_nicename' ) );
+					}
+				}
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_moderators', $forum_mods );
+	}
+
+/**
+ * Output value of forum parent
+ *
+ * @since 2.1.0 bbPress (r3551)
+ */
+function bbp_form_forum_parent() {
+	echo bbp_get_form_forum_parent();
+}
+	/**
+	 * Return value of forum parent
+	 *
+	 * @since 2.1.0 bbPress (r3551)
+	 *
+	 * @return string Value of topic content field
+	 */
+	function bbp_get_form_forum_parent() {
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_id'] ) ) {
+			$forum_parent = (int) $_POST['bbp_forum_id'];
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() ) {
+			$forum_parent = bbp_get_forum_parent_id();
+
+		// No data
+		} else {
+			$forum_parent = 0;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_parent', $forum_parent );
+	}
+
+/**
+ * Output value of forum type
+ *
+ * @since 2.1.0 bbPress (r3563)
+ */
+function bbp_form_forum_type() {
+	echo bbp_get_form_forum_type();
+}
+	/**
+	 * Return value of forum type
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @return string Value of topic content field
+	 */
+	function bbp_get_form_forum_type() {
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_type'] ) ) {
+			$forum_type = sanitize_key( $_POST['bbp_forum_type'] );
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() ) {
+			$forum_type = bbp_get_forum_type();
+
+		// No data
+		} else {
+			$forum_type = 'forum';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_type', $forum_type );
+	}
+
+/**
+ * Output value of forum visibility
+ *
+ * @since 2.1.0 bbPress (r3563)
+ */
+function bbp_form_forum_visibility() {
+	echo bbp_get_form_forum_visibility();
+}
+	/**
+	 * Return value of forum visibility
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @return string Value of topic content field
+	 */
+	function bbp_get_form_forum_visibility() {
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_visibility'] ) ) {
+			$forum_visibility = sanitize_key( $_POST['bbp_forum_visibility'] );
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() ) {
+			$forum_visibility = bbp_get_forum_visibility();
+
+		// No data
+		} else {
+			$forum_visibility = bbpress()->public_status_id;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_visibility', $forum_visibility );
+	}
+
+/**
+ * Output checked value of forum subscription
+ *
+ * @since 2.5.0 bbPress (r5156)
+ */
+function bbp_form_forum_subscribed() {
+	echo bbp_get_form_forum_subscribed();
+}
+	/**
+	 * Return checked value of forum subscription
+	 *
+	 * @since 2.5.0 bbPress (r5156)
+	 *
+	 * @return string Checked value of forum subscription
+	 */
+	function bbp_get_form_forum_subscribed() {
+
+		// Default value
+		$forum_subscribed = false;
+
+		// Get _POST data
+		if ( bbp_is_forum_form_post_request() && isset( $_POST['bbp_forum_subscription'] ) ) {
+			$forum_subscribed = (bool) $_POST['bbp_forum_subscription'];
+
+		// Get edit data
+		} elseif ( bbp_is_forum_edit() || bbp_is_reply_edit() ) {
+			$post_author      = (int) bbp_get_global_post_field( 'post_author', 'raw' );
+			$forum_subscribed = bbp_is_user_subscribed( $post_author, bbp_get_forum_id() );
+
+		// Get current status
+		} elseif ( bbp_is_single_forum() ) {
+			$forum_subscribed = bbp_is_user_subscribed( bbp_get_current_user_id(), bbp_get_forum_id() );
+		}
+
+		// Get checked output
+		$checked = checked( $forum_subscribed, true, false );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_subscribed', $checked, $forum_subscribed );
+	}
+
+/** Form Dropdowns ************************************************************/
+
+/**
+ * Output value forum type dropdown
+ *
+ * @since 2.1.0 bbPress (r3563)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_forum_type
+ *  - tab: Deprecated. Tabindex
+ *  - forum_id: Forum id
+ *  - selected: Override the selected option
+ */
+function bbp_form_forum_type_dropdown( $args = array() ) {
+	echo bbp_get_form_forum_type_dropdown( $args );
+}
+	/**
+	 * Return the forum type dropdown
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_forum_type
+	 *  - tab: Deprecated. Tabindex
+	 *  - forum_id: Forum id
+	 *  - selected: Override the selected option
+	 * @return string HTML select list for selecting forum type
+	 */
+	function bbp_get_form_forum_type_dropdown( $args = array() ) {
+
+		// Backpat for handling passing of a forum ID as integer
+		if ( is_int( $args ) ) {
+			$forum_id = (int) $args;
+			$args     = array();
+		} else {
+			$forum_id = 0;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_forum_type',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'forum_id'     => $forum_id,
+			'selected'     => false
+		), 'forum_type_select' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_forum_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value was passed
+			} else {
+
+				// Edit topic
+				if ( bbp_is_forum_edit() ) {
+					$r['forum_id'] = bbp_get_forum_id( $r['forum_id'] );
+					$r['selected'] = bbp_get_forum_type( $r['forum_id'] );
+
+				// New topic
+				} else {
+					$r['selected'] = bbp_get_public_status_id();
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ) ?>" id="<?php echo esc_attr( $r['select_id'] ) ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_forum_types( $r['forum_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_type_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/**
+ * Output value forum status dropdown
+ *
+ * @since 2.1.0 bbPress (r3563)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_forum_status
+ *  - tab: Deprecated. Tabindex
+ *  - forum_id: Forum id
+ *  - selected: Override the selected option
+ */
+function bbp_form_forum_status_dropdown( $args = array() ) {
+	echo bbp_get_form_forum_status_dropdown( $args );
+}
+	/**
+	 * Return the forum status dropdown
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_forum_status
+	 *  - tab: Deprecated. Tabindex
+	 *  - forum_id: Forum id
+	 *  - selected: Override the selected option
+	 * @return string HTML select list for selecting forum status
+	 */
+	function bbp_get_form_forum_status_dropdown( $args = array() ) {
+
+		// Backpat for handling passing of a forum ID
+		if ( is_int( $args ) ) {
+			$forum_id = (int) $args;
+			$args     = array();
+		} else {
+			$forum_id = 0;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_forum_status',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'forum_id'     => $forum_id,
+			'selected'     => false
+		), 'forum_status_select' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_forum_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value was passed
+			} else {
+
+				// Edit topic
+				if ( bbp_is_forum_edit() ) {
+					$r['forum_id'] = bbp_get_forum_id( $r['forum_id'] );
+					$r['selected'] = bbp_get_forum_status( $r['forum_id'] );
+
+				// New topic
+				} else {
+					$r['selected'] = bbp_get_public_status_id();
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ) ?>" id="<?php echo esc_attr( $r['select_id'] ) ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_forum_statuses( $r['forum_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_status_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/**
+ * Output value forum visibility dropdown
+ *
+ * @since 2.1.0 bbPress (r3563)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_forum_visibility
+ *  - tab: Deprecated. Tabindex
+ *  - forum_id: Forum id
+ *  - selected: Override the selected option
+ */
+function bbp_form_forum_visibility_dropdown( $args = array() ) {
+	echo bbp_get_form_forum_visibility_dropdown( $args );
+}
+	/**
+	 * Return the forum visibility dropdown
+	 *
+	 * @since 2.1.0 bbPress (r3563)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_forum_visibility
+	 *  - tab: Deprecated. Tabindex
+	 *  - forum_id: Forum id
+	 *  - selected: Override the selected option
+	 * @return string HTML select list for selecting forum visibility
+	 */
+	function bbp_get_form_forum_visibility_dropdown( $args = array() ) {
+
+		// Backpat for handling passing of a forum ID
+		if ( is_int( $args ) ) {
+			$forum_id = (int) $args;
+			$args     = array();
+		} else {
+			$forum_id = 0;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_forum_visibility',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'forum_id'     => $forum_id,
+			'selected'     => false
+		), 'forum_type_select' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_forum_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value was passed
+			} else {
+
+				// Edit topic
+				if ( bbp_is_forum_edit() ) {
+					$r['forum_id'] = bbp_get_forum_id( $r['forum_id'] );
+					$r['selected'] = bbp_get_forum_visibility( $r['forum_id'] );
+
+				// New topic
+				} else {
+					$r['selected'] = bbp_get_public_status_id();
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ) ?>" id="<?php echo esc_attr( $r['select_id'] ) ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_forum_visibilities( $r['forum_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_forum_type_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/**
+ * Verify if a POST request came from a failed forum attempt.
+ *
+ * Used to avoid cross-site request forgeries when checking posted forum form
+ * content.
+ *
+ * @see bbp_forum_form_fields()
+ *
+ * @since 2.6.0 bbPress (r5558)
+ *
+ * @return boolean True if is a post request with valid nonce
+ */
+function bbp_is_forum_form_post_request() {
+
+	// Bail if not a post request
+	if ( ! bbp_is_post_request() ) {
+		return false;
+	}
+
+	// Creating a new forum
+	if ( bbp_verify_nonce_request( 'bbp-new-forum' ) ) {
+		return true;
+	}
+
+	// Editing an existing forum
+	if ( bbp_verify_nonce_request( 'bbp-edit-forum_' . bbp_get_forum_id() ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/** Feeds *********************************************************************/
+
+/**
+ * Output the link for the forum feed
+ *
+ * @since 2.0.0 bbPress (r3172)
+ *
+ * @param int $forum_id Optional. Forum ID.
+ */
+function bbp_forum_topics_feed_link( $forum_id = 0 ) {
+	echo bbp_get_forum_topics_feed_link( $forum_id );
+}
+	/**
+	 * Retrieve the link for the forum feed
+	 *
+	 * @since 2.0.0 bbPress (r3172)
+	 *
+	 * @param int $forum_id Optional. Forum ID.
+	 *
+	 * @return string
+	 */
+	function bbp_get_forum_topics_feed_link( $forum_id = 0 ) {
+
+		// Validate forum id
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Forum is valid
+		if ( ! empty( $forum_id ) ) {
+
+			// Define local variable(s)
+			$link = '';
+
+			// Pretty permalinks
+			if ( get_option( 'permalink_structure' ) ) {
+
+				// Forum link
+				$url = trailingslashit( bbp_get_forum_permalink( $forum_id ) ) . 'feed';
+				$url = user_trailingslashit( $url, 'single_feed' );
+
+			// Unpretty permalinks
+			} else {
+				$url = home_url( add_query_arg( array(
+					'feed'                    => 'rss2',
+					bbp_get_forum_post_type() => get_post_field( 'post_name', $forum_id )
+				) ) );
+			}
+
+			$link = '<a href="' . esc_url( $url ) . '" class="bbp-forum-rss-link topics"><span>' . esc_attr__( 'Topics', 'bbpress' ) . '</span></a>';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_topics_feed_link', $link, $url, $forum_id );
+	}
+
+/**
+ * Output the link for the forum replies feed
+ *
+ * @since 2.0.0 bbPress (r3172)
+ *
+ * @param int $forum_id Optional. Forum ID.
+ */
+function bbp_forum_replies_feed_link( $forum_id = 0 ) {
+	echo bbp_get_forum_replies_feed_link( $forum_id );
+}
+	/**
+	 * Retrieve the link for the forum replies feed
+	 *
+	 * @since 2.0.0 bbPress (r3172)
+	 *
+	 * @param int $forum_id Optional. Forum ID.
+	 *
+	 * @return string
+	 */
+	function bbp_get_forum_replies_feed_link( $forum_id = 0 ) {
+
+		// Validate forum id
+		$forum_id = bbp_get_forum_id( $forum_id );
+
+		// Forum is valid
+		if ( ! empty( $forum_id ) ) {
+
+			// Define local variable(s)
+			$link = '';
+
+			// Pretty permalinks
+			if ( get_option( 'permalink_structure' ) ) {
+
+				// Forum link
+				$url = trailingslashit( bbp_get_forum_permalink( $forum_id ) ) . 'feed';
+				$url = user_trailingslashit( $url, 'single_feed' );
+				$url = add_query_arg( array( 'type' => 'reply' ), $url );
+
+			// Unpretty permalinks
+			} else {
+				$url = home_url( add_query_arg( array(
+					'type'                    => 'reply',
+					'feed'                    => 'rss2',
+					bbp_get_forum_post_type() => get_post_field( 'post_name', $forum_id )
+				) ) );
+			}
+
+			$link = '<a href="' . esc_url( $url ) . '" class="bbp-forum-rss-link replies"><span>' . esc_html__( 'Replies', 'bbpress' ) . '</span></a>';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_replies_feed_link', $link, $url, $forum_id );
+	}
Index: /tags/2.6.6/src/includes/index.php
===================================================================
--- /tags/2.6.6/src/includes/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/replies/capabilities.php
===================================================================
--- /tags/2.6.6/src/includes/replies/capabilities.php	(revision 7162)
+++ /tags/2.6.6/src/includes/replies/capabilities.php	(revision 7162)
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * bbPress Reply Capabilites
+ *
+ * Used to map reply capabilities to WordPress's existing capabilities.
+ *
+ * @package bbPress
+ * @subpackage Capabilities
+ */
+
+/**
+ * Return reply capabilities
+ *
+ * @since 2.0.0 bbPress (r2593)
+ *
+ * @return array Reply capabilities
+ */
+function bbp_get_reply_caps() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_caps', array(
+		'edit_posts'          => 'edit_replies',
+		'edit_others_posts'   => 'edit_others_replies',
+		'publish_posts'       => 'publish_replies',
+		'read_private_posts'  => 'read_private_replies',
+		'delete_posts'        => 'delete_replies',
+		'delete_others_posts' => 'delete_others_replies'
+	) );
+}
+
+/**
+ * Maps topic capabilities
+ *
+ * @since 2.2.0 bbPress (r4242)
+ *
+ * @param array  $caps    Capabilities for meta capability.
+ * @param string $cap     Capability name.
+ * @param int    $user_id User id.
+ * @param array  $args    Arguments.
+ *
+ * @return array Actual capabilities for meta capability
+ */
+function bbp_map_reply_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+
+		/** Reading ***********************************************************/
+
+		case 'read_reply' :
+
+			// User cannot spectate
+			if ( ! user_can( $user_id, 'spectate' ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Do some post ID based logic
+			} else {
+
+				// Bail if no post ID
+				if ( empty( $args[0] ) ) {
+					break;
+				}
+
+				// Get the post.
+				$_post = get_post( $args[0] );
+				if ( ! empty( $_post ) ) {
+
+					// Get post type object
+					$post_type = get_post_type_object( $_post->post_type );
+
+					// Post is public
+					if ( bbp_get_public_status_id() === $_post->post_status ) {
+						$caps = array( 'spectate' );
+
+					// User is author so allow read
+					} elseif ( (int) $user_id === (int) $_post->post_author ) {
+						$caps = array( 'spectate' );
+
+					// Moderators can always edit forum content
+					} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+						$caps = array( 'spectate' );
+
+					// Unknown so map to private posts
+					} else {
+						$caps = array( $post_type->cap->read_private_posts );
+					}
+				}
+			}
+
+			break;
+
+		/** Publishing ********************************************************/
+
+		case 'publish_replies' :
+
+			// Moderators can always publish
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		/** Editing ***********************************************************/
+
+		// Used primarily in wp-admin
+		case 'edit_replies'        :
+		case 'edit_others_replies' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+
+			// Otherwise, check forum
+			} else {
+				$forum_id = bbp_get_forum_id();
+
+				// Moderators can always edit forum content
+				if ( user_can( $user_id, 'moderate', $forum_id ) ) {
+					$caps = array( 'spectate' );
+
+				// Fallback to do_not_allow
+				} else {
+					$caps = array( 'do_not_allow' );
+				}
+			}
+
+			break;
+
+		// Used everywhere
+		case 'edit_reply' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post.
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always edit forum content
+				} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+					$caps = array( 'spectate' );
+
+				// Allow author or mod to edit if not in admin, unless past edit lock time
+				} elseif ( ! is_admin() && ( (int) $user_id === (int) $_post->post_author ) ) {
+
+					// If editing...
+					if ( bbp_is_reply_edit() ) {
+
+						// Only allow if not past the edit-lock period
+						$caps = ! bbp_past_edit_lock( $_post->post_date_gmt )
+							? array( $post_type->cap->edit_posts )
+							: array( 'do_not_allow' );
+
+					// Otherwise...
+					} else {
+						$caps = array( $post_type->cap->edit_posts );
+					}
+
+				// Fallback to edit_others_posts.
+				} else {
+					$caps = array( $post_type->cap->edit_others_posts );
+				}
+			}
+
+			break;
+
+		/** Deleting **********************************************************/
+
+		case 'delete_reply' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always edit forum content
+				} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+					$caps = array( 'spectate' );
+
+				// User is author so allow delete if not in admin
+				} elseif ( ! is_admin() && ( (int) $user_id === (int) $_post->post_author ) ) {
+					$caps = array( $post_type->cap->delete_posts );
+
+				// Unknown so map to delete_others_posts
+				} else {
+					$caps = array( $post_type->cap->delete_others_posts );
+				}
+			}
+
+			break;
+
+		// Moderation override
+		case 'delete_replies'        :
+		case 'delete_others_replies' :
+
+			// Moderators can always delete
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		/** Admin *************************************************************/
+
+		case 'bbp_replies_admin' :
+			$caps = array( 'edit_replies' );
+			break;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_reply_meta_caps', $caps, $cap, $user_id, $args );
+}
Index: /tags/2.6.6/src/includes/replies/functions.php
===================================================================
--- /tags/2.6.6/src/includes/replies/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/replies/functions.php	(revision 7162)
@@ -0,0 +1,2446 @@
+<?php
+
+/**
+ * bbPress Reply Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Insert ********************************************************************/
+
+/**
+ * A wrapper for wp_insert_post() that also includes the necessary meta values
+ * for the reply to function properly.
+ *
+ * @since 2.0.0 bbPress (r3349)
+ *
+ * @param array $reply_data Forum post data
+ * @param arrap $reply_meta Forum meta data
+ */
+function bbp_insert_reply( $reply_data = array(), $reply_meta = array() ) {
+
+	// Parse arguments against default values
+	$reply_data = bbp_parse_args( $reply_data, array(
+		'post_parent'    => 0, // topic ID
+		'post_type'      => bbp_get_reply_post_type(),
+		'post_author'    => bbp_get_current_user_id(),
+		'post_password'  => '',
+		'post_content'   => '',
+		'post_title'     => '',
+		'menu_order'     => bbp_get_topic_reply_count( $reply_data['post_parent'], true ) + 1,
+		'comment_status' => 'closed'
+	), 'insert_reply' );
+
+	// Possibly override status based on parent topic
+	if ( ! empty( $reply_data['post_parent'] ) && empty( $reply_data['post_status'] ) ) {
+		$reply_data['post_status'] = bbp_get_topic_status( $reply_data['post_parent'] );
+	}
+
+	// Insert reply
+	$reply_id = wp_insert_post( $reply_data, false );
+
+	// Bail if no reply was added
+	if ( empty( $reply_id ) ) {
+		return false;
+	}
+
+	// Parse arguments against default values
+	$reply_meta = bbp_parse_args( $reply_meta, array(
+		'author_ip' => bbp_current_author_ip(),
+		'forum_id'  => 0,
+		'topic_id'  => 0,
+		'reply_to'  => 0
+	), 'insert_reply_meta' );
+
+	// Insert reply meta
+	foreach ( $reply_meta as $meta_key => $meta_value ) {
+
+		// Prefix if not prefixed
+		if ( '_bbp_' !== substr( $meta_key, 0, 5 ) ) {
+			$meta_key = '_bbp_' . $meta_key;
+		}
+
+		// Update the meta
+		update_post_meta( $reply_id, $meta_key, $meta_value );
+	}
+
+	// Update the reply and hierarchy
+	bbp_update_reply( $reply_id, $reply_meta['topic_id'], $reply_meta['forum_id'], array(), $reply_data['post_author'], false, $reply_meta['reply_to'] );
+
+	/**
+	 * Fires after reply has been inserted via `bbp_insert_reply`.
+	 *
+	 * @since 2.6.0 bbPress (r6036)
+	 *
+	 * @param int $reply_id               The reply id.
+	 * @param int $reply_meta['topic_id'] The reply topic meta.
+	 * @param int $reply_meta['forum_id'] The reply forum meta.
+	 */
+	do_action( 'bbp_insert_reply', (int) $reply_id, (int) $reply_meta['topic_id'], (int) $reply_meta['forum_id'] );
+
+	// Return reply_id
+	return $reply_id;
+}
+
+/**
+ * Update counts after a reply is inserted via `bbp_insert_reply`.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $reply_id The reply id.
+ * @param int $topic_id The topic id.
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_insert_reply_update_counts( $reply_id = 0, $topic_id = 0, $forum_id = 0 ) {
+
+	// If the reply is public, update the reply counts.
+	if ( bbp_is_reply_published( $reply_id ) ) {
+		bbp_increase_topic_reply_count( $topic_id );
+		bbp_increase_forum_reply_count( $forum_id );
+
+	// If the reply isn't public only update the reply hidden counts.
+	} else {
+		bbp_increase_topic_reply_count_hidden( $topic_id );
+		bbp_increase_forum_reply_count_hidden( $forum_id );
+	}
+}
+
+/** Post Form Handlers ********************************************************/
+
+/**
+ * Handles the front end reply submission
+ *
+ * @since 2.0.0 bbPress (r2574)
+ *
+ * @param string $action The requested action to compare this function to
+ *                    id, anonymous data, reply author, edit (false), and
+ *                    the reply to id
+ */
+function bbp_new_reply_handler( $action = '' ) {
+
+	// Bail if action is not bbp-new-reply
+	if ( 'bbp-new-reply' !== $action ) {
+		return;
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-new-reply' ) ) {
+		bbp_add_error( 'bbp_new_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Define local variable(s)
+	$topic_id = $forum_id = $reply_author = $reply_to = 0;
+	$reply_title = $reply_content = $terms = '';
+	$anonymous_data = array();
+
+	/** Reply Author **********************************************************/
+
+	// User is anonymous
+	if ( bbp_is_anonymous() ) {
+
+		// Filter anonymous data (variable is used later)
+		$anonymous_data = bbp_filter_anonymous_post_data();
+
+		// Anonymous data checks out, so set cookies, etc...
+		bbp_set_current_anonymous_user_data( $anonymous_data );
+
+	// User is logged in
+	} else {
+
+		// User cannot create replies
+		if ( ! current_user_can( 'publish_replies' ) ) {
+			bbp_add_error( 'bbp_reply_permission', __( '<strong>Error</strong>: You do not have permission to reply.', 'bbpress' ) );
+		}
+
+		// Reply author is current user
+		$reply_author = bbp_get_current_user_id();
+	}
+
+	/** Topic ID **************************************************************/
+
+	// Topic id was not passed
+	if ( empty( $_POST['bbp_topic_id'] ) ) {
+		bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID is missing.', 'bbpress' ) );
+
+	// Topic id is not a number
+	} elseif ( ! is_numeric( $_POST['bbp_topic_id'] ) ) {
+		bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID must be a number.', 'bbpress' ) );
+
+	// Topic id might be valid
+	} else {
+
+		// Get the topic id
+		$posted_topic_id = intval( $_POST['bbp_topic_id'] );
+
+		// Topic id is a negative number
+		if ( 0 > $posted_topic_id ) {
+			bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID cannot be a negative number.', 'bbpress' ) );
+
+		// Topic does not exist
+		} elseif ( ! bbp_get_topic( $posted_topic_id ) ) {
+			bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic does not exist.', 'bbpress' ) );
+
+		// Use the POST'ed topic id
+		} else {
+			$topic_id = $posted_topic_id;
+		}
+	}
+
+	/** Forum ID **************************************************************/
+
+	// Try to use the forum id of the topic
+	if ( ! isset( $_POST['bbp_forum_id'] ) && ! empty( $topic_id ) ) {
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+	// Error check the POST'ed forum id
+	} elseif ( isset( $_POST['bbp_forum_id'] ) ) {
+
+		// Empty Forum id was passed
+		if ( empty( $_POST['bbp_forum_id'] ) ) {
+			bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
+
+		// Forum id is not a number
+		} elseif ( ! is_numeric( $_POST['bbp_forum_id'] ) ) {
+			bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID must be a number.', 'bbpress' ) );
+
+		// Forum id might be valid
+		} else {
+
+			// Get the forum id
+			$posted_forum_id = intval( $_POST['bbp_forum_id'] );
+
+			// Forum id is empty
+			if ( 0 === $posted_forum_id ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
+
+			// Forum id is a negative number
+			} elseif ( 0 > $posted_forum_id ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID cannot be a negative number.', 'bbpress' ) );
+
+			// Forum does not exist
+			} elseif ( ! bbp_get_forum( $posted_forum_id ) ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum does not exist.', 'bbpress' ) );
+
+			// Use the POST'ed forum id
+			} else {
+				$forum_id = $posted_forum_id;
+			}
+		}
+	}
+
+	// Forum exists
+	if ( ! empty( $forum_id ) ) {
+
+		// Forum is a category
+		if ( bbp_is_forum_category( $forum_id ) ) {
+			bbp_add_error( 'bbp_new_reply_forum_category', __( '<strong>Error</strong>: This forum is a category. No replies can be created in this forum.', 'bbpress' ) );
+
+		// Forum is not a category
+		} else {
+
+			// Forum is closed and user cannot access
+			if ( bbp_is_forum_closed( $forum_id ) && ! current_user_can( 'edit_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_reply_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new replies.', 'bbpress' ) );
+			}
+
+			// Forum is private and user cannot access
+			if ( bbp_is_forum_private( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_reply_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new replies in it.', 'bbpress' ) );
+
+			// Forum is hidden and user cannot access
+			} elseif ( bbp_is_forum_hidden( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_reply_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new replies in it.', 'bbpress' ) );
+			}
+		}
+	}
+
+	/** Unfiltered HTML *******************************************************/
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_reply'] ) && wp_create_nonce( 'bbp-unfiltered-html-reply_' . $topic_id ) === $_POST['_bbp_unfiltered_html_reply'] ) {
+		remove_filter( 'bbp_new_reply_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_new_reply_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_new_reply_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Reply Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_reply_title'] ) ) {
+		$reply_title = sanitize_text_field( $_POST['bbp_reply_title'] );
+	}
+
+	// Filter and sanitize
+	$reply_title = apply_filters( 'bbp_new_reply_pre_title', $reply_title );
+
+	// Title too long
+	if ( bbp_is_title_too_long( $reply_title ) ) {
+		bbp_add_error( 'bbp_reply_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Reply Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_reply_content'] ) ) {
+		$reply_content = $_POST['bbp_reply_content'];
+	}
+
+	// Filter and sanitize
+	$reply_content = apply_filters( 'bbp_new_reply_pre_content', $reply_content );
+
+	// No reply content
+	if ( empty( $reply_content ) ) {
+		bbp_add_error( 'bbp_reply_content', __( '<strong>Error</strong>: Your reply cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Reply Flooding ********************************************************/
+
+	if ( ! bbp_check_for_flood( $anonymous_data, $reply_author ) ) {
+		bbp_add_error( 'bbp_reply_flood', __( '<strong>Error</strong>: Slow down; you move too fast.', 'bbpress' ) );
+	}
+
+	/** Reply Duplicate *******************************************************/
+
+	if ( ! bbp_check_for_duplicate( array( 'post_type' => bbp_get_reply_post_type(), 'post_author' => $reply_author, 'post_content' => $reply_content, 'post_parent' => $topic_id, 'anonymous_data' => $anonymous_data ) ) ) {
+		bbp_add_error( 'bbp_reply_duplicate', __( '<strong>Error</strong>: Duplicate reply detected; it looks as though you&#8217;ve already said that.', 'bbpress' ) );
+	}
+
+	/** Reply Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content, true ) ) {
+		bbp_add_error( 'bbp_reply_moderation', __( '<strong>Error</strong>: Your reply cannot be created at this time.', 'bbpress' ) );
+	}
+
+	/** Reply Status **********************************************************/
+
+	// Maybe put into moderation
+	if ( bbp_is_topic_pending( $topic_id ) || ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content ) ) {
+		$reply_status = bbp_get_pending_status_id();
+
+	// Default
+	} else {
+		$reply_status = bbp_get_public_status_id();
+	}
+
+	/** Reply To **************************************************************/
+
+	// Handle Reply To of the reply; $_REQUEST for non-JS submissions
+	if ( isset( $_REQUEST['bbp_reply_to'] ) ) {
+		$reply_to = bbp_validate_reply_to( $_REQUEST['bbp_reply_to'] );
+	}
+
+	/** Topic Closed **********************************************************/
+
+	// If topic is closed, moderators can still reply
+	if ( bbp_is_topic_closed( $topic_id ) && ! current_user_can( 'moderate', $topic_id ) ) {
+		bbp_add_error( 'bbp_reply_topic_closed', __( '<strong>Error</strong>: Topic is closed.', 'bbpress' ) );
+	}
+
+	/** Topic Tags ************************************************************/
+
+	// Either replace terms
+	if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', $topic_id ) && ! empty( $_POST['bbp_topic_tags'] ) ) {
+		$terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
+
+	// ...or remove them.
+	} elseif ( isset( $_POST['bbp_topic_tags'] ) ) {
+		$terms = '';
+
+	// Existing terms
+	} else {
+		$terms = bbp_get_topic_tag_names( $topic_id );
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_new_reply_pre_extras', $topic_id, $forum_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $reply_data as an array
+	// Just in time manipulation of reply data before being created
+	$reply_data = apply_filters( 'bbp_new_reply_pre_insert', array(
+		'post_author'    => $reply_author,
+		'post_title'     => $reply_title,
+		'post_content'   => $reply_content,
+		'post_status'    => $reply_status,
+		'post_parent'    => $topic_id,
+		'post_type'      => bbp_get_reply_post_type(),
+		'comment_status' => 'closed',
+		'menu_order'     => bbp_get_topic_reply_count( $topic_id, true ) + 1
+	) );
+
+	// Insert reply
+	$reply_id = wp_insert_post( $reply_data, true );
+
+	/** No Errors *************************************************************/
+
+	// Check for missing reply_id or error
+	if ( ! empty( $reply_id ) && ! is_wp_error( $reply_id ) ) {
+
+		/** Topic Tags ********************************************************/
+
+		// Just in time manipulation of reply terms before being edited
+		$terms = apply_filters( 'bbp_new_reply_pre_set_terms', $terms, $topic_id, $reply_id );
+
+		// Insert terms
+		$terms = wp_set_post_terms( $topic_id, $terms, bbp_get_topic_tag_tax_id(), false );
+
+		// Term error
+		if ( is_wp_error( $terms ) ) {
+			bbp_add_error( 'bbp_reply_tags', __( '<strong>Error</strong>: There was a problem adding the tags to the topic.', 'bbpress' ) );
+		}
+
+		/** Trash Check *******************************************************/
+
+		// If this reply starts as trash, add it to pre_trashed_replies
+		// for the topic, so it is properly restored.
+		if ( bbp_is_topic_trash( $topic_id ) || ( $reply_data['post_status'] === bbp_get_trash_status_id() ) ) {
+
+			// Trash the reply
+			wp_trash_post( $reply_id );
+
+			// Only add to pre-trashed array if topic is trashed
+			if ( bbp_is_topic_trash( $topic_id ) ) {
+
+				// Get pre_trashed_replies for topic
+				$pre_trashed_meta = get_post_meta( $topic_id, '_bbp_pre_trashed_replies', true );
+
+				// Format the meta value
+				$pre_trashed_replies = is_array( $pre_trashed_meta )
+					? array_filter( $pre_trashed_meta )
+					: array();
+
+				// Add this reply to the end of the existing replies
+				$pre_trashed_replies[] = $reply_id;
+
+				// Update the pre_trashed_reply post meta
+				update_post_meta( $topic_id, '_bbp_pre_trashed_replies', $pre_trashed_replies );
+			}
+
+		/** Spam Check ********************************************************/
+
+		// If reply or topic are spam, officially spam this reply
+		} elseif ( bbp_is_topic_spam( $topic_id ) || ( $reply_data['post_status'] === bbp_get_spam_status_id() ) ) {
+			add_post_meta( $reply_id, '_bbp_spam_meta_status', bbp_get_public_status_id() );
+
+			// Only add to pre-spammed array if topic is spam
+			if ( bbp_is_topic_spam( $topic_id ) ) {
+
+				// Get pre_spammed_replies for topic
+				$pre_trashed_meta = get_post_meta( $topic_id, '_bbp_pre_spammed_replies', true );
+
+				// Format the meta value
+				$pre_spammed_replies = is_array( $pre_trashed_meta )
+					? array_filter( $pre_trashed_meta )
+					: array();
+
+				// Add this reply to the end of the existing replies
+				$pre_spammed_replies[] = $reply_id;
+
+				// Update the pre_spammed_replies post meta
+				update_post_meta( $topic_id, '_bbp_pre_spammed_replies', $pre_spammed_replies );
+			}
+		}
+
+		/** Update counts, etc... *********************************************/
+
+		do_action( 'bbp_new_reply', $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author, false, $reply_to );
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_new_reply_post_extras', $reply_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to = bbp_get_redirect_to();
+
+		// Get the reply URL
+		$reply_url = bbp_get_reply_url( $reply_id, $redirect_to );
+
+		// Allow to be filtered
+		$reply_url = apply_filters( 'bbp_new_reply_redirect_to', $reply_url, $redirect_to, $reply_id );
+
+		/** Successful Save ***************************************************/
+
+		// Redirect back to new reply
+		bbp_redirect( $reply_url );
+
+	/** Errors ****************************************************************/
+
+	// WP_Error
+	} elseif ( is_wp_error( $reply_id ) ) {
+		bbp_add_error( 'bbp_reply_error', sprintf( __( '<strong>Error</strong>: The following problem(s) occurred: %s', 'bbpress' ), $reply_id->get_error_message() ) );
+
+	// Generic error
+	} else {
+		bbp_add_error( 'bbp_reply_error', __( '<strong>Error</strong>: The reply was not created.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handles the front end edit reply submission
+ *
+ * @param string $action The requested action to compare this function to
+ *                    id, anonymous data, reply author, bool true (for edit),
+ *                    and the reply to id
+ */
+function bbp_edit_reply_handler( $action = '' ) {
+
+	// Bail if action is not bbp-edit-reply
+	if ( 'bbp-edit-reply' !== $action ) {
+		return;
+	}
+
+	// Define local variable(s)
+	$revisions_removed = false;
+	$reply = $reply_id = $reply_to = $reply_author = $topic_id = $forum_id = 0;
+	$reply_title = $reply_content = $reply_edit_reason = $terms = '';
+	$anonymous_data = array();
+
+	/** Reply *****************************************************************/
+
+	// Reply id was not passed
+	if ( empty( $_POST['bbp_reply_id'] ) ) {
+		bbp_add_error( 'bbp_edit_reply_id', __( '<strong>Error</strong>: Reply ID not found.', 'bbpress' ) );
+		return;
+
+	// Reply id was passed
+	} elseif ( is_numeric( $_POST['bbp_reply_id'] ) ) {
+		$reply_id = (int) $_POST['bbp_reply_id'];
+		$reply    = bbp_get_reply( $reply_id );
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-edit-reply_' . $reply_id ) ) {
+		bbp_add_error( 'bbp_edit_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Reply does not exist
+	if ( empty( $reply ) ) {
+		bbp_add_error( 'bbp_edit_reply_not_found', __( '<strong>Error</strong>: The reply you want to edit was not found.', 'bbpress' ) );
+		return;
+
+	// Reply exists
+	} else {
+
+		// Check users ability to create new reply
+		if ( ! bbp_is_reply_anonymous( $reply_id ) ) {
+
+			// User cannot edit this reply
+			if ( ! current_user_can( 'edit_reply', $reply_id ) ) {
+				bbp_add_error( 'bbp_edit_reply_permission', __( '<strong>Error</strong>: You do not have permission to edit that reply.', 'bbpress' ) );
+				return;
+			}
+
+			// Set reply author
+			$reply_author = bbp_get_reply_author_id( $reply_id );
+
+		// It is an anonymous post
+		} else {
+
+			// Filter anonymous data
+			$anonymous_data = bbp_filter_anonymous_post_data();
+		}
+	}
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_reply'] ) && wp_create_nonce( 'bbp-unfiltered-html-reply_' . $reply_id ) === $_POST['_bbp_unfiltered_html_reply'] ) {
+		remove_filter( 'bbp_edit_reply_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_edit_reply_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_edit_reply_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Reply Topic ***********************************************************/
+
+	$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+	/** Topic Forum ***********************************************************/
+
+	$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+	// Forum exists
+	if ( ! empty( $forum_id ) && ( $forum_id !== bbp_get_reply_forum_id( $reply_id ) ) ) {
+
+		// Forum is a category
+		if ( bbp_is_forum_category( $forum_id ) ) {
+			bbp_add_error( 'bbp_edit_reply_forum_category', __( '<strong>Error</strong>: This forum is a category. No replies can be created in this forum.', 'bbpress' ) );
+
+		// Forum is not a category
+		} else {
+
+			// Forum is closed and user cannot access
+			if ( bbp_is_forum_closed( $forum_id ) && ! current_user_can( 'edit_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_reply_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new replies.', 'bbpress' ) );
+			}
+
+			// Forum is private and user cannot access
+			if ( bbp_is_forum_private( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_reply_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new replies in it.', 'bbpress' ) );
+
+			// Forum is hidden and user cannot access
+			} elseif ( bbp_is_forum_hidden( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_reply_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new replies in it.', 'bbpress' ) );
+			}
+		}
+	}
+
+	/** Reply Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_reply_title'] ) ) {
+		$reply_title = sanitize_text_field( $_POST['bbp_reply_title'] );
+	}
+
+	// Filter and sanitize
+	$reply_title = apply_filters( 'bbp_edit_reply_pre_title', $reply_title, $reply_id );
+
+	// Title too long
+	if ( bbp_is_title_too_long( $reply_title ) ) {
+		bbp_add_error( 'bbp_reply_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Reply Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_reply_content'] ) ) {
+		$reply_content = $_POST['bbp_reply_content'];
+	}
+
+	// Filter and sanitize
+	$reply_content = apply_filters( 'bbp_edit_reply_pre_content', $reply_content, $reply_id );
+
+	// No reply content
+	if ( empty( $reply_content ) ) {
+		bbp_add_error( 'bbp_edit_reply_content', __( '<strong>Error</strong>: Your reply cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Reply Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content, true ) ) {
+		bbp_add_error( 'bbp_reply_moderation', __( '<strong>Error</strong>: Your reply cannot be edited at this time.', 'bbpress' ) );
+	}
+
+	/** Reply Status **********************************************************/
+
+	// Maybe put into moderation
+	if ( ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content ) ) {
+
+		// Set post status to pending if public
+		if ( bbp_get_public_status_id() === $reply->post_status ) {
+			$reply_status = bbp_get_pending_status_id();
+		}
+
+	// Use existing post_status
+	} else {
+		$reply_status = $reply->post_status;
+	}
+
+	/** Reply To **************************************************************/
+
+	// Handle Reply To of the reply; $_REQUEST for non-JS submissions
+	if ( isset( $_REQUEST['bbp_reply_to'] ) && current_user_can( 'moderate', $reply_id ) ) {
+		$reply_to = bbp_validate_reply_to( $_REQUEST['bbp_reply_to'], $reply_id );
+	} elseif ( bbp_thread_replies() ) {
+		$reply_to = bbp_get_reply_to( $reply_id );
+	}
+
+	/** Topic Tags ************************************************************/
+
+	// Either replace terms
+	if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', $topic_id ) && ! empty( $_POST['bbp_topic_tags'] ) ) {
+		$terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
+
+	// ...or remove them.
+	} elseif ( isset( $_POST['bbp_topic_tags'] ) ) {
+		$terms = '';
+
+	// Existing terms
+	} else {
+		$terms = bbp_get_topic_tag_names( $topic_id );
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_edit_reply_pre_extras', $reply_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $reply_data as an array
+	// Just in time manipulation of reply data before being edited
+	$reply_data = apply_filters( 'bbp_edit_reply_pre_insert', array(
+		'ID'           => $reply_id,
+		'post_title'   => $reply_title,
+		'post_content' => $reply_content,
+		'post_status'  => $reply_status,
+		'post_parent'  => $topic_id,
+		'post_author'  => $reply_author,
+		'post_type'    => bbp_get_reply_post_type()
+	) );
+
+	// Toggle revisions to avoid duplicates
+	if ( post_type_supports( bbp_get_reply_post_type(), 'revisions' ) ) {
+		$revisions_removed = true;
+		remove_post_type_support( bbp_get_reply_post_type(), 'revisions' );
+	}
+
+	// Insert reply
+	$reply_id = wp_update_post( $reply_data );
+
+	// Toggle revisions back on
+	if ( true === $revisions_removed ) {
+		$revisions_removed = false;
+		add_post_type_support( bbp_get_reply_post_type(), 'revisions' );
+	}
+
+	/** Topic Tags ************************************************************/
+
+	// Just in time manipulation of reply terms before being edited
+	$terms = apply_filters( 'bbp_edit_reply_pre_set_terms', $terms, $topic_id, $reply_id );
+
+	// Insert terms
+	$terms = wp_set_post_terms( $topic_id, $terms, bbp_get_topic_tag_tax_id(), false );
+
+	// Term error
+	if ( is_wp_error( $terms ) ) {
+		bbp_add_error( 'bbp_reply_tags', __( '<strong>Error</strong>: There was a problem adding the tags to the topic.', 'bbpress' ) );
+	}
+
+	/** No Errors *************************************************************/
+
+	if ( ! empty( $reply_id ) && ! is_wp_error( $reply_id ) ) {
+
+		// Update counts, etc...
+		do_action( 'bbp_edit_reply', $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author , true, $reply_to );
+
+		/** Revisions *********************************************************/
+
+		// Update locks
+		update_post_meta( $reply_id, '_edit_last', bbp_get_current_user_id() );
+		delete_post_meta( $reply_id, '_edit_lock' );
+
+		// Revision Reason
+		if ( ! empty( $_POST['bbp_reply_edit_reason'] ) ) {
+			$reply_edit_reason = sanitize_text_field( $_POST['bbp_reply_edit_reason'] );
+		}
+
+		// Update revision log
+		if ( ! empty( $_POST['bbp_log_reply_edit'] ) && ( "1" === $_POST['bbp_log_reply_edit'] ) ) {
+			$revision_id = wp_save_post_revision( $reply_id );
+			if ( ! empty( $revision_id ) ) {
+				bbp_update_reply_revision_log( array(
+					'reply_id'    => $reply_id,
+					'revision_id' => $revision_id,
+					'author_id'   => bbp_get_current_user_id(),
+					'reason'      => $reply_edit_reason
+				) );
+			}
+		}
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_edit_reply_post_extras', $reply_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to = bbp_get_redirect_to();
+
+		// Get the reply URL
+		$reply_url = bbp_get_reply_url( $reply_id, $redirect_to );
+
+		// Allow to be filtered
+		$reply_url = apply_filters( 'bbp_edit_reply_redirect_to', $reply_url, $redirect_to );
+
+		/** Successful Edit ***************************************************/
+
+		// Redirect back to new reply
+		bbp_redirect( $reply_url );
+
+	/** Errors ****************************************************************/
+
+	} else {
+		$append_error = ( is_wp_error( $reply_id ) && $reply_id->get_error_message() ) ? $reply_id->get_error_message() . ' ' : '';
+		bbp_add_error( 'bbp_reply_error', __( '<strong>Error</strong>: The following problem(s) have been found with your reply:' . $append_error . 'Please try again.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handle all the extra meta stuff from posting a new reply or editing a reply
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param int $topic_id Optional. Topic id
+ * @param int $forum_id Optional. Forum id
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $author_id Author id
+ * @param bool $is_edit Optional. Is the post being edited? Defaults to false.
+ * @param int $reply_to Optional. Reply to id
+ */
+function bbp_update_reply( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $author_id = 0, $is_edit = false, $reply_to = 0 ) {
+
+	// Validate the ID's passed from 'bbp_new_reply' action
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$forum_id = bbp_get_forum_id( $forum_id );
+	$reply_to = bbp_validate_reply_to( $reply_to, $reply_id );
+
+	// Bail if there is no reply
+	if ( empty( $reply_id ) ) {
+		return;
+	}
+
+	// Check author_id
+	if ( empty( $author_id ) ) {
+		$author_id = bbp_get_current_user_id();
+	}
+
+	// Check topic_id
+	if ( empty( $topic_id ) ) {
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+	}
+
+	// Check forum_id
+	if ( ! empty( $topic_id ) && empty( $forum_id ) ) {
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+	}
+
+	// If anonymous post, store name, email, website and ip in post_meta.
+	if ( ! empty( $anonymous_data ) ) {
+
+		// Update anonymous meta data (not cookies)
+		bbp_update_anonymous_post_author( $reply_id, $anonymous_data, bbp_get_reply_post_type() );
+
+		// Set transient for throttle check (only on new, not edit)
+		if ( empty( $is_edit ) ) {
+			set_transient( '_bbp_' . bbp_current_author_ip() . '_last_posted', time(), HOUR_IN_SECONDS );
+		}
+	}
+
+	// Handle Subscription Checkbox
+	if ( bbp_is_subscriptions_active() && ! empty( $author_id ) && ! empty( $topic_id ) ) {
+
+		// Check if subscribed
+		$subscribed = bbp_is_user_subscribed( $author_id, $topic_id );
+
+		// Check for action
+		$subscheck  = ( ! empty( $_POST['bbp_topic_subscription'] ) && ( 'bbp_subscribe' === $_POST['bbp_topic_subscription'] ) )
+			? true
+			: false;
+
+		// Subscribed and unsubscribing
+		if ( ( true === $subscribed ) && ( false === $subscheck ) ) {
+			bbp_remove_user_subscription( $author_id, $topic_id );
+
+		// Not subscribed and subscribing
+		} elseif ( ( false === $subscribed ) && ( true === $subscheck ) ) {
+			bbp_add_user_subscription( $author_id, $topic_id );
+		}
+	}
+
+	// Reply meta relating to reply position in tree
+	bbp_update_reply_forum_id( $reply_id, $forum_id );
+	bbp_update_reply_topic_id( $reply_id, $topic_id );
+	bbp_update_reply_to      ( $reply_id, $reply_to );
+
+	// Update associated topic values if this is a new reply
+	if ( empty( $is_edit ) ) {
+
+		// Update poster activity time
+		bbp_update_user_last_posted( $author_id );
+
+		// Update poster IP
+		update_post_meta( $reply_id, '_bbp_author_ip', bbp_current_author_ip(), false );
+
+		// Last active time
+		$last_active_time = get_post_field( 'post_date', $reply_id );
+
+		// Walk up ancestors and do the dirty work
+		bbp_update_reply_walker( $reply_id, $last_active_time, $forum_id, $topic_id, false );
+	}
+
+	// Bump the custom query cache
+	wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
+}
+
+/**
+ * Walk up the ancestor tree from the current reply, and update all the counts
+ *
+ * @since 2.0.0 bbPress (r2884)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param string $last_active_time Optional. Last active time
+ * @param int $forum_id Optional. Forum id
+ * @param int $topic_id Optional. Topic id
+ * @param bool $refresh If set to true, unsets all the previous parameters.
+ *                       Defaults to true
+ */
+function bbp_update_reply_walker( $reply_id, $last_active_time = '', $forum_id = 0, $topic_id = 0, $refresh = true ) {
+
+	// Verify the reply ID
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	// Reply was passed
+	if ( ! empty( $reply_id ) ) {
+
+		// Get the topic ID if none was passed
+		if ( empty( $topic_id ) ) {
+			$topic_id = bbp_get_reply_topic_id( $reply_id );
+		}
+
+		// Get the forum ID if none was passed
+		if ( empty( $forum_id ) ) {
+			$forum_id = bbp_get_reply_forum_id( $reply_id );
+		}
+	}
+
+	// Set the active_id based on topic_id/reply_id
+	$active_id = empty( $reply_id ) ? $topic_id : $reply_id;
+
+	// Setup ancestors array to walk up
+	$ancestors = array_values( array_unique( array_merge( array( $topic_id, $forum_id ), (array) get_post_ancestors( $topic_id ) ) ) );
+
+	// If we want a full refresh, unset any of the possibly passed variables
+	if ( true === $refresh ) {
+		$forum_id = $topic_id = $reply_id = $active_id = $last_active_time = 0;
+	}
+
+	// Walk up ancestors
+	if ( ! empty( $ancestors ) ) {
+		foreach ( $ancestors as $ancestor ) {
+
+			// Reply meta relating to most recent reply
+			if ( bbp_is_reply( $ancestor ) ) {
+				// @todo - hierarchical replies
+
+			// Topic meta relating to most recent reply
+			} elseif ( bbp_is_topic( $ancestor ) ) {
+
+				// Only update if reply is published
+				if ( ! bbp_is_reply_pending( $reply_id ) ) {
+
+					// Last reply and active ID's
+					bbp_update_topic_last_reply_id ( $ancestor, $reply_id  );
+					bbp_update_topic_last_active_id( $ancestor, $active_id );
+
+					// Get the last active time if none was passed
+					$topic_last_active_time = $last_active_time;
+					if ( empty( $last_active_time ) ) {
+						$topic_last_active_time = get_post_field( 'post_date', bbp_get_topic_last_active_id( $ancestor ) );
+					}
+
+					bbp_update_topic_last_active_time( $ancestor, $topic_last_active_time );
+				}
+
+				// Only update reply count if we've deleted a reply
+				if ( in_array( current_filter(), array( 'bbp_deleted_reply', 'save_post' ), true ) ) {
+					bbp_update_topic_reply_count(        $ancestor );
+					bbp_update_topic_reply_count_hidden( $ancestor );
+					bbp_update_topic_voice_count(        $ancestor );
+				}
+
+			// Forum meta relating to most recent topic
+			} elseif ( bbp_is_forum( $ancestor ) ) {
+
+				// Only update if reply is published
+				if ( ! bbp_is_reply_pending( $reply_id ) && ! bbp_is_topic_pending( $topic_id ) ) {
+
+					// Last topic and reply ID's
+					bbp_update_forum_last_topic_id( $ancestor, $topic_id );
+					bbp_update_forum_last_reply_id( $ancestor, $reply_id );
+
+					// Last Active
+					bbp_update_forum_last_active_id( $ancestor, $active_id );
+
+					// Get the last active time if none was passed
+					$forum_last_active_time = $last_active_time;
+					if ( empty( $last_active_time ) ) {
+						$forum_last_active_time = get_post_field( 'post_date', bbp_get_forum_last_active_id( $ancestor ) );
+					}
+
+					bbp_update_forum_last_active_time( $ancestor, $forum_last_active_time );
+				}
+
+				// Only update reply count if we've deleted a reply
+				if ( in_array( current_filter(), array( 'bbp_deleted_reply', 'save_post' ), true ) ) {
+					bbp_update_forum_reply_count( $ancestor );
+				}
+			}
+		}
+	}
+}
+
+/** Reply Updaters ************************************************************/
+
+/**
+ * Update the reply with its forum id it is in
+ *
+ * @since 2.0.0 bbPress (r2855)
+ *
+ * @param int $reply_id Optional. Reply id to update
+ * @param int $forum_id Optional. Forum id
+ * @return bool The forum id of the reply
+ */
+function bbp_update_reply_forum_id( $reply_id = 0, $forum_id = 0 ) {
+
+	// Validation
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// If no forum_id was passed, walk up ancestors and look for forum type
+	if ( empty( $forum_id ) ) {
+
+		// Get ancestors
+		$ancestors = get_post_ancestors( $reply_id );
+
+		// Loop through ancestors
+		if ( ! empty( $ancestors ) ) {
+			foreach ( $ancestors as $ancestor ) {
+
+				// Get first parent that is a forum
+				if ( get_post_field( 'post_type', $ancestor ) === bbp_get_forum_post_type() ) {
+					$forum_id = $ancestor;
+
+					// Found a forum, so exit the loop and continue
+					continue;
+				}
+			}
+		}
+	}
+
+	// Update the forum ID
+	$retval = bbp_update_forum_id( $reply_id, $forum_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_reply_forum_id', $retval, $reply_id, $forum_id );
+}
+
+/**
+ * Update the reply with its topic id it is in
+ *
+ * @since 2.0.0 bbPress (r2855)
+ *
+ * @param int $reply_id Optional. Reply id to update
+ * @param int $topic_id Optional. Topic id
+ * @return bool The topic id of the reply
+ */
+function bbp_update_reply_topic_id( $reply_id = 0, $topic_id = 0 ) {
+
+	// Validation
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// If no topic_id was passed, walk up ancestors and look for topic type
+	if ( empty( $topic_id ) ) {
+
+		// Get ancestors
+		$ancestors = (array) get_post_ancestors( $reply_id );
+
+		// Loop through ancestors
+		if ( ! empty( $ancestors ) ) {
+			foreach ( $ancestors as $ancestor ) {
+
+				// Get first parent that is a topic
+				if ( get_post_field( 'post_type', $ancestor ) === bbp_get_topic_post_type() ) {
+					$topic_id = $ancestor;
+
+					// Found a topic, so exit the loop and continue
+					continue;
+				}
+			}
+		}
+	}
+
+	// Update the topic ID
+	$retval = bbp_update_topic_id( $reply_id, $topic_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_reply_topic_id', $retval, $reply_id, $topic_id );
+}
+
+/*
+ * Update the meta data with its parent reply-to id, of a reply
+ *
+ * @since 2.4.0 bbPress (r4944)
+ *
+ * @param int $reply_id Reply id to update
+ * @param int $reply_to Optional. Reply to id
+ * @return bool The parent reply id of the reply
+ */
+function bbp_update_reply_to( $reply_id = 0, $reply_to = 0 ) {
+
+	// Validation
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$reply_to = bbp_validate_reply_to( $reply_to, $reply_id );
+
+	// Update or delete the `reply_to` postmeta
+	if ( ! empty( $reply_id ) ) {
+
+		// Update the reply to
+		if ( ! empty( $reply_to ) ) {
+			$reply_to = bbp_update_reply_to_id( $reply_id, $reply_to );
+
+		// Delete the reply to
+		} else {
+			delete_post_meta( $reply_id, '_bbp_reply_to' );
+		}
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_reply_to', $reply_to, $reply_id );
+}
+
+/**
+ * Get all ancestors to a reply
+ *
+ * Because settings can be changed, this function does not care if hierarchical
+ * replies are active or to what depth.
+ *
+ * @since 2.6.0 bbPress (r5390)
+ *
+ * @param int $reply_id
+ * @return array
+ */
+function bbp_get_reply_ancestors( $reply_id = 0 ) {
+
+	// Validation
+	$reply_id  = bbp_get_reply_id( $reply_id );
+	$ancestors = array();
+
+	// Reply id is valid
+	if ( ! empty( $reply_id ) ) {
+
+		// Try to get reply parent
+		$reply_to = bbp_get_reply_to( $reply_id );
+
+		// Reply has a hierarchical parent
+		if ( ! empty( $reply_to ) ) {
+
+			// Setup the current ID and current post as an ancestor
+			$id        = $reply_to;
+			$ancestors = array( $reply_to );
+
+			// Get parent reply
+			while ( $ancestor = bbp_get_reply( $id ) ) {
+
+				// Does parent have a parent?
+				$grampy_id = bbp_get_reply_to( $ancestor->ID );
+
+				// Loop detection: If the ancestor has been seen before, break.
+				if ( empty( $ancestor->post_parent ) || ( $grampy_id === $reply_id ) || in_array( $grampy_id, $ancestors, true ) ) {
+					break;
+				}
+
+				$id = $ancestors[] = $grampy_id;
+			}
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_ancestors', $ancestors, $reply_id );
+}
+
+/**
+ * Update the revision log of the reply
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param array $args Supports these args:
+ *  - reply_id: reply id
+ *  - author_id: Author id
+ *  - reason: Reason for editing
+ *  - revision_id: Revision id
+ * @return mixed False on failure, true on success
+ */
+function bbp_update_reply_revision_log( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'reason'      => '',
+		'reply_id'    => 0,
+		'author_id'   => 0,
+		'revision_id' => 0
+	), 'update_reply_revision_log' );
+
+	// Populate the variables
+	$r['reason']      = bbp_format_revision_reason( $r['reason'] );
+	$r['reply_id']    = bbp_get_reply_id( $r['reply_id'] );
+	$r['author_id']   = bbp_get_user_id ( $r['author_id'], false, true );
+	$r['revision_id'] = (int) $r['revision_id'];
+
+	// Get the logs and append the new one to those
+	$revision_log                      = bbp_get_reply_raw_revision_log( $r['reply_id'] );
+	$revision_log[ $r['revision_id'] ] = array( 'author' => $r['author_id'], 'reason' => $r['reason'] );
+
+	// Finally, update
+	update_post_meta( $r['reply_id'], '_bbp_revision_log', $revision_log );
+
+	// Filter & return
+	return apply_filters( 'bbp_update_reply_revision_log', $revision_log, $r['reply_id'] );
+}
+
+/**
+ * Move reply handler
+ *
+ * Handles the front end move reply submission
+ *
+ * @since 2.3.0 bbPress (r4521)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_move_reply_handler( $action = '' ) {
+
+	// Bail if action is not 'bbp-move-reply'
+	if ( 'bbp-move-reply' !== $action ) {
+		return;
+	}
+
+	// Prevent debug notices
+	$move_reply_id = $destination_topic_id = 0;
+	$destination_topic_title = '';
+	$destination_topic = $move_reply = $source_topic = '';
+
+	/** Move Reply ***********************************************************/
+
+	if ( empty( $_POST['bbp_reply_id'] ) ) {
+		bbp_add_error( 'bbp_move_reply_reply_id', __( '<strong>Error</strong>: A reply ID is required', 'bbpress' ) );
+	} else {
+		$move_reply_id = (int) $_POST['bbp_reply_id'];
+	}
+
+	$move_reply = bbp_get_reply( $move_reply_id );
+
+	// Reply exists
+	if ( empty( $move_reply ) ) {
+		bbp_add_error( 'bbp_mover_reply_r_not_found', __( '<strong>Error</strong>: The reply you want to move was not found.', 'bbpress' ) );
+	}
+
+	/** Topic to Move From ***************************************************/
+
+	// Get the current topic a reply is in
+	$source_topic = bbp_get_topic( $move_reply->post_parent );
+
+	// No topic
+	if ( empty( $source_topic ) ) {
+		bbp_add_error( 'bbp_move_reply_source_not_found', __( '<strong>Error</strong>: The topic you want to move from was not found.', 'bbpress' ) );
+	}
+
+	// Nonce check failed
+	if ( ! bbp_verify_nonce_request( 'bbp-move-reply_' . $move_reply->ID ) ) {
+		bbp_add_error( 'bbp_move_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Use cannot edit topic
+	if ( ! current_user_can( 'edit_topic', $source_topic->ID ) ) {
+		bbp_add_error( 'bbp_move_reply_source_permission', __( '<strong>Error</strong>: You do not have permission to edit the source topic.', 'bbpress' ) );
+	}
+
+	// How to move
+	if ( ! empty( $_POST['bbp_reply_move_option'] ) ) {
+		$move_option = (string) trim( $_POST['bbp_reply_move_option'] );
+	}
+
+	// Invalid move option
+	if ( empty( $move_option ) || ! in_array( $move_option, array( 'existing', 'topic' ), true ) ) {
+		bbp_add_error( 'bbp_move_reply_option', __( '<strong>Error</strong>: You need to choose a valid move option.', 'bbpress' ) );
+
+	// Valid move option
+	} else {
+
+		// What kind of move
+		switch ( $move_option ) {
+
+			// Into an existing topic
+			case 'existing' :
+
+				// Get destination topic id
+				if ( empty( $_POST['bbp_destination_topic'] ) ) {
+					bbp_add_error( 'bbp_move_reply_destination_id', __( '<strong>Error</strong>: A topic ID is required.', 'bbpress' ) );
+				} else {
+					$destination_topic_id = (int) $_POST['bbp_destination_topic'];
+				}
+
+				// Get the destination topic
+				$destination_topic = bbp_get_topic( $destination_topic_id );
+
+				// No destination topic
+				if ( empty( $destination_topic ) ) {
+					bbp_add_error( 'bbp_move_reply_destination_not_found', __( '<strong>Error</strong>: The topic you want to move to was not found.', 'bbpress' ) );
+				}
+
+				// User cannot edit the destination topic
+				if ( ! current_user_can( 'edit_topic', $destination_topic->ID ) ) {
+					bbp_add_error( 'bbp_move_reply_destination_permission', __( '<strong>Error</strong>: You do not have permission to edit the destination topic.', 'bbpress' ) );
+				}
+
+				// Bump the reply position
+				$reply_position = bbp_get_topic_reply_count( $destination_topic->ID, true ) + 1;
+
+				// Update the reply
+				wp_update_post( array(
+					'ID'          => $move_reply->ID,
+					'post_title'  => '',
+					'post_name'   => false, // will be automatically generated
+					'post_parent' => $destination_topic->ID,
+					'menu_order'  => $reply_position,
+					'guid'        => ''
+				) );
+
+				// Adjust reply meta values
+				bbp_update_reply_topic_id( $move_reply->ID, $destination_topic->ID );
+				bbp_update_reply_forum_id( $move_reply->ID, bbp_get_topic_forum_id( $destination_topic->ID ) );
+
+				break;
+
+			// Move reply to a new topic
+			case 'topic' :
+			default :
+
+				// User needs to be able to publish topics
+				if ( current_user_can( 'publish_topics' ) ) {
+
+					// Use the new title that was passed
+					if ( ! empty( $_POST['bbp_reply_move_destination_title'] ) ) {
+						$destination_topic_title = sanitize_text_field( $_POST['bbp_reply_move_destination_title'] );
+
+					// Use the source topic title
+					} else {
+						$destination_topic_title = $source_topic->post_title;
+					}
+
+					// Update the topic
+					$destination_topic_id = wp_update_post( array(
+						'ID'          => $move_reply->ID,
+						'post_title'  => $destination_topic_title,
+						'post_name'   => false,
+						'post_type'   => bbp_get_topic_post_type(),
+						'post_parent' => $source_topic->post_parent,
+						'guid'        => ''
+					) );
+					$destination_topic = bbp_get_topic( $destination_topic_id );
+
+					// Make sure the new topic knows its a topic
+					bbp_update_topic_topic_id( $move_reply->ID );
+
+					// Shouldn't happen
+					if ( false === $destination_topic_id || is_wp_error( $destination_topic_id ) || empty( $destination_topic ) ) {
+						bbp_add_error( 'bbp_move_reply_destination_reply', __( '<strong>Error</strong>: There was a problem converting the reply into the topic. Please try again.', 'bbpress' ) );
+					}
+
+				// User cannot publish posts
+				} else {
+					bbp_add_error( 'bbp_move_reply_destination_permission', __( '<strong>Error</strong>: You do not have permission to create new topics. The reply could not be converted into a topic.', 'bbpress' ) );
+				}
+
+				break;
+		}
+	}
+
+	// Bail if there are errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors - Clean Up **************************************************/
+
+	// Update counts, etc...
+	do_action( 'bbp_pre_move_reply', $move_reply->ID, $source_topic->ID, $destination_topic->ID );
+
+	/** Date Check ************************************************************/
+
+	// Check if the destination topic is older than the move reply
+	if ( strtotime( $move_reply->post_date ) < strtotime( $destination_topic->post_date ) ) {
+
+		// Set destination topic post_date to 1 second before from reply
+		$destination_post_date = date( 'Y-m-d H:i:s', strtotime( $move_reply->post_date ) - 1 );
+
+		// Update destination topic
+		wp_update_post( array(
+			'ID'            => $destination_topic_id,
+			'post_date'     => $destination_post_date,
+			'post_date_gmt' => get_gmt_from_date( $destination_post_date )
+		) );
+	}
+
+	// Set the last reply ID and freshness to the move_reply
+	$last_reply_id = $move_reply->ID;
+	$freshness     = $move_reply->post_date;
+
+	// Get the reply to
+	$parent = bbp_get_reply_to( $move_reply->ID );
+
+	// Fix orphaned children
+	$children = get_posts( array(
+		'post_type'  => bbp_get_reply_post_type(),
+		'meta_key'   => '_bbp_reply_to',
+		'meta_type'  => 'NUMERIC',
+		'meta_value' => $move_reply->ID,
+	) );
+	foreach ( $children as $child ) {
+		bbp_update_reply_to( $child->ID, $parent );
+	}
+
+	// Remove reply_to from moved reply
+	delete_post_meta( $move_reply->ID, '_bbp_reply_to' );
+
+	// It is a new topic and we need to set some default metas to make
+	// the topic display in bbp_has_topics() list
+	if ( 'topic' === $move_option ) {
+		bbp_update_topic_last_reply_id   ( $destination_topic->ID, $last_reply_id );
+		bbp_update_topic_last_active_id  ( $destination_topic->ID, $last_reply_id );
+		bbp_update_topic_last_active_time( $destination_topic->ID, $freshness     );
+
+	// Otherwise update the existing destination topic
+	} else {
+		bbp_update_topic_last_reply_id   ( $destination_topic->ID );
+		bbp_update_topic_last_active_id  ( $destination_topic->ID );
+		bbp_update_topic_last_active_time( $destination_topic->ID );
+	}
+
+	// Update source topic ID last active
+	bbp_update_topic_last_reply_id   ( $source_topic->ID );
+	bbp_update_topic_last_active_id  ( $source_topic->ID );
+	bbp_update_topic_last_active_time( $source_topic->ID );
+
+	/** Successful Move ******************************************************/
+
+	// Update counts, etc...
+	do_action( 'bbp_post_move_reply', $move_reply->ID, $source_topic->ID, $destination_topic->ID );
+
+	// Redirect back to the topic
+	bbp_redirect( bbp_get_topic_permalink( $destination_topic->ID ) );
+}
+
+/**
+ * Fix counts on reply move
+ *
+ * When a reply is moved, update the counts of source and destination topic
+ * and their forums.
+ *
+ * @since 2.3.0 bbPress (r4521)
+ *
+ * @param int $move_reply_id Move reply id
+ * @param int $source_topic_id Source topic id
+ * @param int $destination_topic_id Destination topic id
+ */
+function bbp_move_reply_count( $move_reply_id, $source_topic_id, $destination_topic_id ) {
+
+	// Forum topic counts
+	bbp_update_forum_topic_count( bbp_get_topic_forum_id( $destination_topic_id ) );
+
+	// Forum reply counts
+	bbp_update_forum_reply_count( bbp_get_topic_forum_id( $destination_topic_id ) );
+
+	// Topic reply counts
+	bbp_update_topic_reply_count( $source_topic_id      );
+	bbp_update_topic_reply_count( $destination_topic_id );
+
+	// Topic hidden reply counts
+	bbp_update_topic_reply_count_hidden( $source_topic_id      );
+	bbp_update_topic_reply_count_hidden( $destination_topic_id );
+
+	// Topic voice counts
+	bbp_update_topic_voice_count( $source_topic_id      );
+	bbp_update_topic_voice_count( $destination_topic_id );
+
+	do_action( 'bbp_move_reply_count', $move_reply_id, $source_topic_id, $destination_topic_id );
+}
+
+/** Reply Actions *************************************************************/
+
+/**
+ * Handles the front end spamming/unspamming and trashing/untrashing/deleting of
+ * replies
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_toggle_reply_handler( $action = '' ) {
+
+	// Bail if required GET actions aren't passed
+	if ( empty( $_GET['reply_id'] ) ) {
+		return;
+	}
+
+	// What's the reply id?
+	$reply_id = bbp_get_reply_id( (int) $_GET['reply_id'] );
+
+	// Get possible reply-handler toggles
+	$toggles = bbp_get_reply_toggles( $reply_id );
+
+	// Bail if action isn't meant for this function
+	if ( ! in_array( $action, $toggles, true ) ) {
+		return;
+	}
+
+	// Make sure reply exists
+	$reply = bbp_get_reply( $reply_id );
+	if ( empty( $reply ) ) {
+		bbp_add_error( 'bbp_toggle_reply_missing', __( '<strong>Error</strong>: This reply could not be found or no longer exists.', 'bbpress' ) );
+		return;
+	}
+
+	// What is the user doing here?
+	if ( ! current_user_can( 'edit_reply', $reply_id ) || ( 'bbp_toggle_reply_trash' === $action && ! current_user_can( 'delete_reply', $reply_id ) ) ) {
+		bbp_add_error( 'bbp_toggle_reply_permission', __( '<strong>Error</strong>: You do not have permission to do that.', 'bbpress' ) );
+		return;
+	}
+
+	// Sub-action?
+	$sub_action = ! empty( $_GET['sub_action'] )
+		? sanitize_key( $_GET['sub_action'] )
+		: false;
+
+	// Preliminary array
+	$post_data = array( 'ID' => $reply_id );
+
+	// Do the reply toggling
+	$retval = bbp_toggle_reply( array(
+		'id'         => $reply_id,
+		'action'     => $action,
+		'sub_action' => $sub_action,
+		'data'       => $post_data
+	) );
+
+	// Do additional reply toggle actions
+	do_action( 'bbp_toggle_reply_handler', $retval['status'], $post_data, $action );
+
+	// Redirect back to reply
+	if ( ( false !== $retval['status'] ) && ! is_wp_error( $retval['status'] ) ) {
+		bbp_redirect( $retval['redirect_to'] );
+
+	// Handle errors
+	} else {
+		bbp_add_error( 'bbp_toggle_reply', $retval['message'] );
+	}
+}
+
+/**
+ * Do the actual reply toggling
+ *
+ * This function is used by `bbp_toggle_reply_handler()` to do the actual heavy
+ * lifting when it comes to toggling replies. It only really makes sense to call
+ * within that context, so if you need to call this function directly, make sure
+ * you're also doing what the handler does too.
+ *
+ * @since 2.6.0 bbPress (r6133)
+ * @access private
+ *
+ * @param array $args
+ */
+function bbp_toggle_reply( $args = array() ) {
+
+	// Parse the arguments
+	$r = bbp_parse_args( $args, array(
+		'id'         => 0,
+		'action'     => '',
+		'sub_action' => '',
+		'data'       => array()
+	) );
+
+	// Build the nonce suffix
+	$nonce_suffix = bbp_get_reply_post_type() . '_' . (int) $r['id'];
+
+	// Default return values
+	$retval = array(
+		'status'      => 0,
+		'message'     => '',
+		'redirect_to' => bbp_get_reply_url( $r['id'], bbp_get_redirect_to() ),
+		'view_all'    => false
+	);
+
+	// What action are we trying to perform?
+	switch ( $r['action'] ) {
+
+		// Toggle approve
+		case 'bbp_toggle_reply_approve' :
+			check_ajax_referer( "approve-{$nonce_suffix}" );
+
+			$is_approve         = bbp_is_reply_pending( $r['id'] );
+			$retval['status']   = $is_approve ? bbp_approve_reply( $r['id'] ) : bbp_unapprove_reply( $r['id'] );
+			$retval['message']  = $is_approve ? __( '<strong>Error</strong>: There was a problem approving the reply.', 'bbpress' ) : __( '<strong>Error</strong>: There was a problem unapproving the reply.', 'bbpress' );
+			$retval['view_all'] = ! $is_approve;
+
+			break;
+
+		// Toggle spam
+		case 'bbp_toggle_reply_spam' :
+			check_ajax_referer( "spam-{$nonce_suffix}" );
+
+			$is_spam            = bbp_is_reply_spam( $r['id'] );
+			$retval['status']   = $is_spam ? bbp_unspam_reply( $r['id'] ) : bbp_spam_reply( $r['id'] );
+			$retval['message']  = $is_spam ? __( '<strong>Error</strong>: There was a problem unmarking the reply as spam.', 'bbpress' ) : __( '<strong>Error</strong>: There was a problem marking the reply as spam.', 'bbpress' );
+			$retval['view_all'] = ! $is_spam;
+
+			break;
+
+		// Toggle trash
+		case 'bbp_toggle_reply_trash' :
+
+			// Which subaction?
+			switch ( $r['sub_action'] ) {
+				case 'trash':
+					check_ajax_referer( "trash-{$nonce_suffix}" );
+
+					$retval['view_all'] = true;
+					$retval['status']   = wp_trash_post( $r['id'] );
+					$retval['message']  = __( '<strong>Error</strong>: There was a problem trashing the reply.', 'bbpress' );
+
+					break;
+
+				case 'untrash':
+					check_ajax_referer( "untrash-{$nonce_suffix}" );
+
+					$retval['status']  = wp_untrash_post( $r['id'] );
+					$retval['message'] = __( '<strong>Error</strong>: There was a problem untrashing the reply.', 'bbpress' );
+
+					break;
+
+				case 'delete':
+					check_ajax_referer( "delete-{$nonce_suffix}" );
+
+					$retval['status']  = wp_delete_post( $r['id'] );
+					$retval['message'] = __( '<strong>Error</strong>: There was a problem deleting the reply.', 'bbpress' );
+
+					break;
+			}
+
+			break;
+	}
+
+	// Add view all if needed
+	if ( ! empty( $retval['view_all'] ) ) {
+		$retval['redirect_to'] = bbp_add_view_all( $retval['redirect_to'], true );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_toggle_reply', $retval, $r, $args );
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Return an associative array of available reply statuses
+ *
+ * @since 2.6.0 bbPress (r5399)
+ *
+ * @param int $reply_id   Optional. Reply id.
+ *
+ * @return array
+ */
+function bbp_get_reply_statuses( $reply_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_statuses', array(
+		bbp_get_public_status_id()  => _x( 'Publish', 'Publish the reply',     'bbpress' ),
+		bbp_get_spam_status_id()    => _x( 'Spam',    'Spam the reply',        'bbpress' ),
+		bbp_get_trash_status_id()   => _x( 'Trash',   'Trash the reply',       'bbpress' ),
+		bbp_get_pending_status_id() => _x( 'Pending', 'Mark reply as pending', 'bbpress' )
+	), $reply_id );
+}
+
+/**
+ * Return array of available reply toggle actions
+ *
+ * @since 2.6.0 bbPress (r6133)
+ *
+ * @param int $reply_id   Optional. Reply id.
+ *
+ * @return array
+ */
+function bbp_get_reply_toggles( $reply_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_toggle_reply_actions', array(
+		'bbp_toggle_reply_spam',
+		'bbp_toggle_reply_trash',
+		'bbp_toggle_reply_approve'
+	), $reply_id );
+}
+
+/**
+ * Return array of public reply statuses.
+ *
+ * @since 2.6.0 bbPress (r6705)
+ *
+ * @return array
+ */
+function bbp_get_public_reply_statuses() {
+	$statuses = array(
+		bbp_get_public_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_public_reply_statuses', $statuses );
+}
+
+/**
+ * Return array of non-public reply statuses.
+ *
+ * @since 2.6.0 bbPress (r6791)
+ *
+ * @return array
+ */
+function bbp_get_non_public_reply_statuses() {
+	$statuses = array(
+		bbp_get_trash_status_id(),
+		bbp_get_spam_status_id(),
+		bbp_get_pending_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_non_public_reply_statuses', $statuses );
+}
+
+/** Reply Actions *************************************************************/
+
+/**
+ * Marks a reply as spam
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $reply_id Reply id
+ * @return mixed False or {@link WP_Error} on failure, reply id on success
+ */
+function bbp_spam_reply( $reply_id = 0 ) {
+
+	// Get reply
+	$reply = bbp_get_reply( $reply_id );
+	if ( empty( $reply ) ) {
+		return $reply;
+	}
+
+	// Bail if already spam
+	if ( bbp_get_spam_status_id() === $reply->post_status ) {
+		return false;
+	}
+
+	// Execute pre spam code
+	do_action( 'bbp_spam_reply', $reply_id );
+
+	// Add the original post status as post meta for future restoration
+	add_post_meta( $reply_id, '_bbp_spam_meta_status', $reply->post_status );
+
+	// Set post status to spam
+	$reply->post_status = bbp_get_spam_status_id();
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update the reply
+	$reply_id = wp_update_post( $reply );
+
+	// Execute post spam code
+	do_action( 'bbp_spammed_reply', $reply_id );
+
+	// Return reply_id
+	return $reply_id;
+}
+
+/**
+ * Unspams a reply
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $reply_id Reply id
+ * @return mixed False or {@link WP_Error} on failure, reply id on success
+ */
+function bbp_unspam_reply( $reply_id = 0 ) {
+
+	// Get reply
+	$reply = bbp_get_reply( $reply_id );
+	if ( empty( $reply ) ) {
+		return $reply;
+	}
+
+	// Bail if already not spam
+	if ( bbp_get_spam_status_id() !== $reply->post_status ) {
+		return false;
+	}
+
+	// Execute pre unspam code
+	do_action( 'bbp_unspam_reply', $reply_id );
+
+	// Get pre spam status
+	$reply->post_status = get_post_meta( $reply_id, '_bbp_spam_meta_status', true );
+
+	// If no previous status, default to publish
+	if ( empty( $reply->post_status ) ) {
+		$reply->post_status = bbp_get_public_status_id();
+	}
+
+	// Delete pre spam meta
+	delete_post_meta( $reply_id, '_bbp_spam_meta_status' );
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update the reply
+	$reply_id = wp_update_post( $reply );
+
+	// Execute post unspam code
+	do_action( 'bbp_unspammed_reply', $reply_id );
+
+	// Return reply_id
+	return $reply_id;
+}
+
+/**
+ * Approves a reply
+ *
+ * @since 2.6.0 bbPress (r5506)
+ *
+ * @param int $reply_id Reply id
+ * @return mixed False or {@link WP_Error} on failure, reply id on success
+ */
+function bbp_approve_reply( $reply_id = 0 ) {
+
+	// Get reply
+	$reply = bbp_get_reply( $reply_id );
+	if ( empty( $reply ) ) {
+		return $reply;
+	}
+
+	// Get new status
+	$status = bbp_get_public_status_id();
+
+	// Bail if already approved
+	if ( $status === $reply->post_status ) {
+		return false;
+	}
+
+	// Execute pre pending code
+	do_action( 'bbp_approve_reply', $reply_id );
+
+	// Set publish status
+	$reply->post_status = $status;
+
+	// Set post date GMT - prevents post_date override in wp_update_post()
+	$reply->post_date_gmt = get_gmt_from_date( $reply->post_date );
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update reply
+	$reply_id = wp_update_post( $reply );
+
+	// Execute post pending code
+	do_action( 'bbp_approved_reply', $reply_id );
+
+	// Return reply_id
+	return $reply_id;
+}
+
+/**
+ * Unapproves a reply
+ *
+ * @since 2.6.0 bbPress (r5506)
+ *
+ * @param int $reply_id Reply id
+ * @return mixed False or {@link WP_Error} on failure, reply id on success
+ */
+function bbp_unapprove_reply( $reply_id = 0 ) {
+
+	// Get reply
+	$reply = bbp_get_reply( $reply_id );
+	if ( empty( $reply ) ) {
+		return $reply;
+	}
+
+	// Get new status
+	$status = bbp_get_pending_status_id();
+
+	// Bail if already pending
+	if ( $status === $reply->post_status ) {
+		return false;
+	}
+
+	// Execute pre open code
+	do_action( 'bbp_unapprove_reply', $reply_id );
+
+	// Set pending status
+	$reply->post_status = $status;
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update reply
+	$reply_id = wp_update_post( $reply );
+
+	// Execute post open code
+	do_action( 'bbp_unapproved_reply', $reply_id );
+
+	// Return reply_id
+	return $reply_id;
+}
+
+/** Before Delete/Trash/Untrash ***********************************************/
+
+/**
+ * Called before deleting a reply
+ */
+function bbp_delete_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_delete_reply', $reply_id );
+}
+
+/**
+ * Called before trashing a reply
+ */
+function bbp_trash_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trash_reply', $reply_id );
+}
+
+/**
+ * Called before untrashing (restoring) a reply
+ */
+function bbp_untrash_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrash_reply', $reply_id );
+}
+
+/** After Delete/Trash/Untrash ************************************************/
+
+/**
+ * Called after deleting a reply
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_deleted_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_deleted_reply', $reply_id );
+}
+
+/**
+ * Called after trashing a reply
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_trashed_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trashed_reply', $reply_id );
+}
+
+/**
+ * Called after untrashing (restoring) a reply
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_untrashed_reply( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrashed_reply', $reply_id );
+}
+
+/** Settings ******************************************************************/
+
+/**
+ * Return the replies per page setting
+ *
+ * @since 2.0.0 bbPress (r3540)
+ *
+ * @param int $default Default replies per page (15)
+ * @return int
+ */
+function bbp_get_replies_per_page( $default = 15 ) {
+
+	// Get database option and cast as integer
+	$retval = get_option( '_bbp_replies_per_page', $default );
+
+	// If return val is empty, set it to default
+	if ( empty( $retval ) ) {
+		$retval = $default;
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_replies_per_page', $retval, $default );
+}
+
+/**
+ * Return the replies per RSS page setting
+ *
+ * @since 2.0.0 bbPress (r3540)
+ *
+ * @param int $default Default replies per page (25)
+ * @return int
+ */
+function bbp_get_replies_per_rss_page( $default = 25 ) {
+
+	// Get database option and cast as integer
+	$retval = get_option( '_bbp_replies_per_rss_page', $default );
+
+	// If return val is empty, set it to default
+	if ( empty( $retval ) ) {
+		$retval = $default;
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_replies_per_rss_page', $retval, $default );
+}
+
+/** Autoembed *****************************************************************/
+
+/**
+ * Check if autoembeds are enabled and hook them in if so
+ *
+ * @since 2.1.0 bbPress (r3752)
+ *
+ * @global WP_Embed $wp_embed
+ */
+function bbp_reply_content_autoembed() {
+	global $wp_embed;
+
+	if ( bbp_use_autoembed() && is_a( $wp_embed, 'WP_Embed' ) ) {
+		add_filter( 'bbp_get_reply_content', array( $wp_embed, 'autoembed' ), 2 );
+	}
+}
+
+/** Filters *******************************************************************/
+
+/**
+ * Used by bbp_has_replies() to add the lead topic post to the posts loop
+ *
+ * This function filters the 'post_where' of the WP_Query, and changes the query
+ * to include both the topic AND its children in the same loop.
+ *
+ * @since 2.1.0 bbPress (r4058)
+ *
+ * @param string $where
+ * @return string
+ */
+function _bbp_has_replies_where( $where = '', $query = false ) {
+
+	/** Bail ******************************************************************/
+
+	// Bail if the sky is falling
+	if ( empty( $where ) || empty( $query ) ) {
+		return $where;
+	}
+
+	// Bail if no post_parent to replace
+	if ( ! is_numeric( $query->get( 'post_parent' ) ) ) {
+		return $where;
+	}
+
+	// Bail if not a topic and reply query
+	if ( array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ) !== $query->get( 'post_type' ) ) {
+		return $where;
+	}
+
+	// Bail if including specific post ID's
+	if ( $query->get( 'post__in' ) ) {
+		return $where;
+	}
+
+	/** Proceed ***************************************************************/
+
+	// Table name for posts
+	$table_name = bbp_db()->prefix . 'posts';
+
+	// Get the topic ID from the post_parent, set in bbp_has_replies()
+	$topic_id   = bbp_get_topic_id( $query->get( 'post_parent' ) );
+
+	// The texts to search for
+	$search     = array(
+		"FROM {$table_name} " ,
+		"WHERE 1=1  AND {$table_name}.post_parent = {$topic_id}",
+		") AND {$table_name}.post_parent = {$topic_id}"
+	);
+
+	// The texts to replace them with
+	$replace     = array(
+		$search[0] . "FORCE INDEX (PRIMARY, post_parent) " ,
+		"WHERE 1=1 AND ({$table_name}.ID = {$topic_id} OR {$table_name}.post_parent = {$topic_id})",
+		") AND ({$table_name}.ID = {$topic_id} OR {$table_name}.post_parent = {$topic_id})"
+	);
+
+	// Try to replace the search text with the replacement
+	$new_where = str_replace( $search, $replace, $where );
+	if ( ! empty( $new_where ) ) {
+		$where = $new_where;
+	}
+
+	return $where;
+}
+
+/** Feeds *********************************************************************/
+
+/**
+ * Output an RSS2 feed of replies, based on the query passed.
+ *
+ * @since 2.0.0 bbPress (r3171)
+ *
+ * @param array $replies_query
+ */
+function bbp_display_replies_feed_rss2( $replies_query = array() ) {
+
+	// User cannot access forum this topic is in
+	if ( bbp_is_single_topic() && ! bbp_user_can_view_forum( array( 'forum_id' => bbp_get_topic_forum_id() ) ) ) {
+		return;
+	}
+
+	// Adjust the title based on context
+	if ( bbp_is_single_topic() ) {
+		$title = get_wp_title_rss();
+	} elseif ( ! bbp_show_lead_topic() ) {
+		$title = get_bloginfo_rss( 'name' ) . ' &#187; ' .  __( 'All Posts',   'bbpress' );
+	} else {
+		$title = get_bloginfo_rss( 'name' ) . ' &#187; ' .  __( 'All Replies', 'bbpress' );
+	}
+
+	$title = apply_filters( 'wp_title_rss', $title );
+
+	// Display the feed
+	header( 'Content-Type: ' . feed_content_type( 'rss2' ) . '; charset=' . get_option( 'blog_charset' ), true );
+	header( 'Status: 200 OK' );
+	echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . '>'; ?>
+
+	<rss version="2.0"
+		xmlns:content="http://purl.org/rss/1.0/modules/content/"
+		xmlns:wfw="http://wellformedweb.org/CommentAPI/"
+		xmlns:dc="http://purl.org/dc/elements/1.1/"
+		xmlns:atom="http://www.w3.org/2005/Atom"
+
+		<?php do_action( 'bbp_feed' ); ?>
+	>
+
+	<channel>
+
+		<title><?php echo $title; // Already escaped ?></title>
+		<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
+		<link><?php self_link(); ?></link>
+		<description><?php //?></description>
+		<lastBuildDate><?php echo date( 'r' ); ?></lastBuildDate>
+		<generator><?php echo esc_url_raw( 'https://bbpress.org/?v=' . convert_chars( bbp_get_version() ) ); ?></generator>
+		<language><?php bloginfo_rss( 'language' ); ?></language>
+
+		<?php do_action( 'bbp_feed_head' ); ?>
+
+		<?php if ( bbp_is_single_topic() ) : ?>
+			<?php if ( bbp_user_can_view_forum( array( 'forum_id' => bbp_get_topic_forum_id() ) ) ) : ?>
+				<?php if ( bbp_show_lead_topic() ) : ?>
+
+					<item>
+						<guid><?php bbp_topic_permalink(); ?></guid>
+						<title><![CDATA[<?php bbp_topic_title(); ?>]]></title>
+						<link><?php bbp_topic_permalink(); ?></link>
+						<pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ); ?></pubDate>
+						<dc:creator><?php the_author(); ?></dc:creator>
+
+						<description>
+							<![CDATA[
+							<p><?php printf( __( 'Replies: %s', 'bbpress' ), bbp_get_topic_reply_count() ); ?></p>
+							<?php bbp_topic_content(); ?>
+							]]>
+						</description>
+
+						<?php rss_enclosure(); ?>
+
+						<?php do_action( 'bbp_feed_item' ); ?>
+
+					</item>
+
+				<?php endif; ?>
+			<?php endif; ?>
+		<?php endif; ?>
+
+		<?php if ( bbp_has_replies( $replies_query ) ) : ?>
+			<?php while ( bbp_replies() ) : bbp_the_reply(); ?>
+
+				<item>
+					<guid><?php bbp_reply_url(); ?></guid>
+					<title><![CDATA[<?php bbp_reply_title(); ?>]]></title>
+					<link><?php bbp_reply_url(); ?></link>
+					<pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ); ?></pubDate>
+					<dc:creator><?php the_author() ?></dc:creator>
+
+					<description>
+						<![CDATA[
+						<?php bbp_reply_content(); ?>
+						]]>
+					</description>
+
+					<?php rss_enclosure(); ?>
+
+					<?php do_action( 'bbp_feed_item' ); ?>
+
+				</item>
+
+			<?php endwhile; ?>
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_feed_footer' ); ?>
+
+	</channel>
+	</rss>
+
+<?php
+
+	// We're done here
+	exit();
+}
+
+/** Permissions ***************************************************************/
+
+/**
+ * Redirect if unauthorized user is attempting to edit a reply
+ *
+ * @since 2.1.0 bbPress (r3605)
+ */
+function bbp_check_reply_edit() {
+
+	// Bail if not editing a topic
+	if ( ! bbp_is_reply_edit() ) {
+		return;
+	}
+
+	// User cannot edit topic, so redirect back to reply
+	if ( ! current_user_can( 'edit_reply', bbp_get_reply_id() ) ) {
+		bbp_redirect( bbp_get_reply_url() );
+	}
+}
+
+/** Reply Position ************************************************************/
+
+/**
+ * Update the position of the reply.
+ *
+ * The reply position is stored in the menu_order column of the posts table.
+ * This is done to prevent using a meta_query to retrieve posts in the proper
+ * freshness order. By updating the menu_order accordingly, we're able to
+ * leverage core WordPress query ordering much more effectively.
+ *
+ * @since 2.1.0 bbPress (r3933)
+ *
+ * @param int $reply_id
+ * @param int $reply_position
+ *
+ * @return mixed
+ */
+function bbp_update_reply_position( $reply_id = 0, $reply_position = false ) {
+
+	// Bail if reply_id is empty
+	$reply_id = bbp_get_reply_id( $reply_id );
+	if ( empty( $reply_id ) ) {
+		return false;
+	}
+
+	// Prepare the reply position
+	$reply_position = is_numeric( $reply_position )
+		? (int) $reply_position
+		: bbp_get_reply_position_raw( $reply_id, bbp_get_reply_topic_id( $reply_id ) );
+
+	// Get the current reply position
+	$current_position = get_post_field( 'menu_order', $reply_id );
+
+	// Bail if no change
+	if ( $reply_position === $current_position ) {
+		return false;
+	}
+
+	// Filters not removed
+	$removed = false;
+
+	// Toggle revisions off as we are not altering content
+	if ( has_filter( 'clean_post_cache', 'bbp_clean_post_cache' ) ) {
+		$removed = true;
+		remove_filter( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
+	}
+
+	// Update the replies' 'menu_order' with the reply position
+	$bbp_db = bbp_db();
+	$bbp_db->update( $bbp_db->posts, array( 'menu_order' => $reply_position ), array( 'ID' => $reply_id ) );
+	clean_post_cache( $reply_id );
+
+	// Toggle revisions back on
+	if ( true === $removed ) {
+		$removed = false;
+		add_filter( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
+	}
+
+	return (int) $reply_position;
+}
+
+/**
+ * Get the position of a reply by querying the DB directly for the replies
+ * of a given topic.
+ *
+ * @since 2.1.0 bbPress (r3933)
+ *
+ * @param int $reply_id
+ * @param int $topic_id
+ */
+function bbp_get_reply_position_raw( $reply_id = 0, $topic_id = 0 ) {
+
+	// Get required data
+	$reply_position = 0;
+	$reply_id       = bbp_get_reply_id( $reply_id );
+	$topic_id       = ! empty( $topic_id )
+		? bbp_get_topic_id( $topic_id )
+		: bbp_get_reply_topic_id( $reply_id );
+
+	// If reply is actually the first post in a topic, return 0
+	if ( $reply_id !== $topic_id ) {
+
+		// Make sure the topic has replies before running another query
+		$reply_count = bbp_get_topic_reply_count( $topic_id, false );
+		if ( ! empty( $reply_count ) ) {
+
+			// Get reply id's
+			$topic_replies = bbp_get_all_child_ids( $topic_id, bbp_get_reply_post_type() );
+			if ( ! empty( $topic_replies ) ) {
+
+				// Reverse replies array and search for current reply position
+				$topic_replies  = array_reverse( $topic_replies );
+				$reply_position = array_search( (string) $reply_id, $topic_replies );
+
+				// Bump the position to compensate for the lead topic post
+				$reply_position++;
+			}
+		}
+	}
+
+	return (int) $reply_position;
+}
+
+/** Hierarchical Replies ******************************************************/
+
+/**
+ * Are replies threaded?
+ *
+ * @since 2.4.0 bbPress (r4944)
+ * @since 2.6.0 bbPress (r6245) Always false on user profile reply pages
+ *
+ * @param bool $default Optional. Default value true
+ *
+ * @return bool Are replies threaded?
+ */
+function bbp_thread_replies() {
+	$depth = bbp_thread_replies_depth();
+	$allow = bbp_allow_threaded_replies();
+
+	// Never thread replies on user profile pages. It looks weird, and we know
+	// it is undesirable for the majority of installations.
+	if ( bbp_is_single_user_replies() ) {
+		$retval = false;
+	} else {
+		$retval = (bool) ( ( $depth >= 2 ) && ( true === $allow ) );
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_thread_replies', $retval, $depth, $allow );
+}
+
+/**
+ * List threaded replies
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+function bbp_list_replies( $args = array() ) {
+
+	// Get bbPress
+	$bbp = bbpress();
+
+	// Reset the reply depth
+	$bbp->reply_query->reply_depth = 0;
+
+	// In reply loop
+	$bbp->reply_query->in_the_loop = true;
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, array(
+		'walker'       => new BBP_Walker_Reply(),
+		'max_depth'    => bbp_thread_replies_depth(),
+		'style'        => 'ul',
+		'callback'     => null,
+		'end_callback' => null,
+		'page'         => 1,
+		'per_page'     => -1
+	), 'list_replies' );
+
+	// Get replies to loop through in $_replies
+	echo '<ul>' . $r['walker']->paged_walk( $bbp->reply_query->posts, $r['max_depth'], $r['page'], $r['per_page'], $r ) . '</ul>';
+
+	$bbp->max_num_pages            = $r['walker']->max_pages;
+	$bbp->reply_query->in_the_loop = false;
+}
+
+/**
+ * Validate a `reply_to` field for hierarchical replies
+ *
+ * Checks for 2 scenarios:
+ * -- The reply to ID is actually a reply
+ * -- The reply to ID does not match the current reply
+ *
+ * @see https://bbpress.trac.wordpress.org/ticket/2588
+ * @see https://bbpress.trac.wordpress.org/ticket/2586
+ *
+ * @since 2.5.4 bbPress (r5377)
+ *
+ * @param int $reply_to
+ * @param int $reply_id
+ *
+ * @return int $reply_to
+ */
+function bbp_validate_reply_to( $reply_to = 0, $reply_id = 0 ) {
+
+	// The parent reply must actually be a reply
+	if ( ! bbp_is_reply( $reply_to ) ) {
+		$reply_to = 0;
+	}
+
+	// The parent reply cannot be itself
+	if ( $reply_id === $reply_to ) {
+		$reply_to = 0;
+	}
+
+	return (int) $reply_to;
+}
Index: /tags/2.6.6/src/includes/replies/index.php
===================================================================
--- /tags/2.6.6/src/includes/replies/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/replies/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/replies/template.php
===================================================================
--- /tags/2.6.6/src/includes/replies/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/replies/template.php	(revision 7162)
@@ -0,0 +1,2761 @@
+<?php
+
+/**
+ * bbPress Reply Template Tags
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Post Type *****************************************************************/
+
+/**
+ * Return the unique id of the custom post type for replies
+ *
+ * @since 2.0.0 bbPress (r2857)
+ *
+ */
+function bbp_reply_post_type() {
+	echo bbp_get_reply_post_type();
+}
+	/**
+	 * Return the unique id of the custom post type for replies
+	 *
+	 * @since 2.0.0 bbPress (r2857)
+	 *
+	 *                        post type id
+	 * @return string The unique reply post type id
+	 */
+	function bbp_get_reply_post_type() {
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_post_type', bbpress()->reply_post_type );
+	}
+
+/**
+ * Return array of labels used by the reply post type
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_reply_post_type_labels() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_post_type_labels', array(
+		'name'                     => esc_attr__( 'Replies',                    'bbpress' ),
+		'menu_name'                => esc_attr__( 'Replies',                    'bbpress' ),
+		'singular_name'            => esc_attr_x( 'Reply', 'noun',              'bbpress' ),
+		'all_items'                => esc_attr__( 'All Replies',                'bbpress' ),
+		'add_new'                  => esc_attr__( 'Add New',                    'bbpress' ),
+		'add_new_item'             => esc_attr__( 'Create New Reply',           'bbpress' ),
+		'edit'                     => esc_attr__( 'Edit',                       'bbpress' ),
+		'edit_item'                => esc_attr__( 'Edit Reply',                 'bbpress' ),
+		'new_item'                 => esc_attr__( 'New Reply',                  'bbpress' ),
+		'view'                     => esc_attr__( 'View Reply',                 'bbpress' ),
+		'view_item'                => esc_attr__( 'View Reply',                 'bbpress' ),
+		'view_items'               => esc_attr__( 'View Replies',               'bbpress' ),
+		'search_items'             => esc_attr__( 'Search Replies',             'bbpress' ),
+		'not_found'                => esc_attr__( 'No replies found',           'bbpress' ),
+		'not_found_in_trash'       => esc_attr__( 'No replies found in Trash',  'bbpress' ),
+		'filter_items_list'        => esc_attr__( 'Filter replies list',        'bbpress' ),
+		'items_list'               => esc_attr__( 'Replies list',               'bbpress' ),
+		'items_list_navigation'    => esc_attr__( 'Replies list navigation',    'bbpress' ),
+		'parent_item_colon'        => esc_attr__( 'Parent Topic:',              'bbpress' ),
+		'archives'                 => esc_attr__( 'Forum Replies',              'bbpress' ),
+		'attributes'               => esc_attr__( 'Reply Attributes',           'bbpress' ),
+		'insert_into_item'         => esc_attr__( 'Insert into reply',          'bbpress' ),
+		'uploaded_to_this_item'    => esc_attr__( 'Uploaded to this reply',     'bbpress' ),
+		'featured_image'           => esc_attr__( 'Reply Image',                'bbpress' ),
+		'set_featured_image'       => esc_attr__( 'Set reply image',            'bbpress' ),
+		'remove_featured_image'    => esc_attr__( 'Remove reply image',         'bbpress' ),
+		'use_featured_image'       => esc_attr__( 'Use as reply image',         'bbpress' ),
+		'item_published'           => esc_attr__( 'Reply published.',           'bbpress' ),
+		'item_published_privately' => esc_attr__( 'Reply published privately.', 'bbpress' ),
+		'item_reverted_to_draft'   => esc_attr__( 'Reply reverted to draft.',   'bbpress' ),
+		'item_scheduled'           => esc_attr__( 'Reply scheduled.',           'bbpress' ),
+		'item_updated'             => esc_attr__( 'Reply updated.',             'bbpress' )
+	) );
+}
+
+/**
+ * Return array of reply post type rewrite settings
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_reply_post_type_rewrite() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_post_type_rewrite', array(
+		'slug'       => bbp_get_reply_slug(),
+		'with_front' => false
+	) );
+}
+
+/**
+ * Return array of features the reply post type supports
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_reply_post_type_supports() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_reply_post_type_supports', array(
+		'title',
+		'editor',
+		'revisions'
+	) );
+}
+
+/** Reply Loop Functions ******************************************************/
+
+/**
+ * The main reply loop. WordPress makes this easy for us
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param array $args All the arguments supported by {@link WP_Query}
+ * @return object Multidimensional array of reply information
+ */
+function bbp_has_replies( $args = array() ) {
+
+	/** Defaults **************************************************************/
+
+	// Other defaults
+	$default_reply_search   = bbp_sanitize_search_request( 'rs' );
+	$default_post_parent    = ( bbp_is_single_topic() ) ? bbp_get_topic_id() : 'any';
+	$default_post_type      = ( bbp_is_single_topic() && bbp_show_lead_topic() ) ? bbp_get_reply_post_type() : array( bbp_get_topic_post_type(), bbp_get_reply_post_type() );
+	$default_thread_replies = (bool) ( bbp_is_single_topic() && bbp_thread_replies() );
+
+	// Default query args
+	$default = array(
+		'post_type'              => $default_post_type,         // Only replies
+		'post_parent'            => $default_post_parent,       // Of this topic
+		'posts_per_page'         => bbp_get_replies_per_page(), // This many
+		'paged'                  => bbp_get_paged(),            // On this page
+		'orderby'                => 'date',                     // Sorted by date
+		'order'                  => 'ASC',                      // Oldest to newest
+		'hierarchical'           => $default_thread_replies,    // Hierarchical replies
+		'ignore_sticky_posts'    => true,                       // Stickies not supported
+		'update_post_term_cache' => false,                      // No terms to cache
+
+		// Conditionally prime the cache for all related posts
+		'update_post_family_cache' => true
+	);
+
+	// Only add 's' arg if searching for replies
+	// See https://bbpress.trac.wordpress.org/ticket/2607
+	if ( ! empty( $default_reply_search ) ) {
+		$default['s'] = $default_reply_search;
+	}
+
+	// What are the default allowed statuses (based on user caps)
+	if ( bbp_get_view_all( 'edit_others_replies' ) ) {
+
+		// Default view=all statuses
+		$post_statuses = array_keys( bbp_get_topic_statuses() );
+
+		// Add support for private status
+		if ( current_user_can( 'read_private_replies' ) ) {
+			$post_statuses[] = bbp_get_private_status_id();
+		}
+
+		// Join post statuses together
+		$default['post_status'] = $post_statuses;
+
+	// Lean on the 'perm' query var value of 'readable' to provide statuses
+	} else {
+		$default['perm'] = 'readable';
+	}
+
+	/** Setup *****************************************************************/
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, $default, 'has_replies' );
+
+	// Set posts_per_page value if replies are threaded
+	$replies_per_page = (int) $r['posts_per_page'];
+	if ( true === $r['hierarchical'] ) {
+		$r['posts_per_page'] = -1;
+	}
+
+	// Get bbPress
+	$bbp = bbpress();
+
+	// Call the query
+	$bbp->reply_query = new WP_Query( $r );
+
+	// Maybe prime the post author caches
+	if ( ! empty( $r['update_post_family_cache'] ) ) {
+		bbp_update_post_family_caches( $bbp->reply_query->posts );
+	}
+
+	// Add pagination values to query object
+	$bbp->reply_query->posts_per_page = (int) $replies_per_page;
+	$bbp->reply_query->paged          = (int) $r['paged'];
+
+	// Never home, regardless of what parse_query says
+	$bbp->reply_query->is_home        = false;
+
+	// Reset is_single if single topic
+	if ( bbp_is_single_topic() ) {
+		$bbp->reply_query->is_single = true;
+	}
+
+	// Only add reply to if query returned results
+	if ( ! empty( $bbp->reply_query->found_posts ) ) {
+
+		// Get reply to for each reply
+		foreach ( $bbp->reply_query->posts as &$post ) {
+
+			// Check for reply post type
+			if ( bbp_get_reply_post_type() === $post->post_type ) {
+				$reply_to = bbp_get_reply_to( $post->ID );
+
+				// Make sure it's a reply to a reply
+				if ( empty( $reply_to ) || ( bbp_get_reply_topic_id( $post->ID ) === $reply_to ) ) {
+					$reply_to = 0;
+				}
+
+				// Add reply_to to the post object so we can walk it later
+				$post->reply_to = $reply_to;
+			}
+		}
+	}
+
+	// Only add pagination if query returned results
+	if ( ! empty( $bbp->reply_query->found_posts ) && ! empty( $bbp->reply_query->posts_per_page ) ) {
+
+		// Figure out total pages
+		if ( true === $r['hierarchical'] ) {
+			$walker      = new BBP_Walker_Reply();
+			$total_pages = ceil( $walker->get_number_of_root_elements( $bbp->reply_query->posts ) / $bbp->reply_query->posts_per_page );
+		} else {
+
+			// Total for pagination boundaries
+			$total_pages = ( $bbp->reply_query->posts_per_page === $bbp->reply_query->found_posts )
+				? 1
+				: ceil( $bbp->reply_query->found_posts / $bbp->reply_query->posts_per_page );
+
+			// Pagination settings with filter
+			$bbp_replies_pagination = apply_filters( 'bbp_replies_pagination', array(
+				'base'    => bbp_get_replies_pagination_base( bbp_get_topic_id() ),
+				'total'   => $total_pages,
+				'current' => $bbp->reply_query->paged
+			) );
+
+			// Add pagination to query object
+			$bbp->reply_query->pagination_links = bbp_paginate_links( $bbp_replies_pagination );
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_has_replies', $bbp->reply_query->have_posts(), $bbp->reply_query );
+}
+
+/**
+ * Whether there are more replies available in the loop
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @return object Replies information
+ */
+function bbp_replies() {
+
+	// Put into variable to check against next
+	$have_posts = bbpress()->reply_query->have_posts();
+
+	// Reset the post data when finished
+	if ( empty( $have_posts ) ) {
+		wp_reset_postdata();
+	}
+
+	return $have_posts;
+}
+
+/**
+ * Loads up the current reply in the loop
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @return object Reply information
+ */
+function bbp_the_reply() {
+	return bbpress()->reply_query->the_post();
+}
+
+/**
+ * Output reply id
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param $reply_id Optional. Used to check emptiness
+ */
+function bbp_reply_id( $reply_id = 0 ) {
+	echo bbp_get_reply_id( $reply_id );
+}
+	/**
+	 * Return the id of the reply in a replies loop
+	 *
+	 * @since 2.0.0 bbPress (r2553)
+	 *
+	 * @param $reply_id Optional. Used to check emptiness
+	 * @return int The reply id
+	 */
+	function bbp_get_reply_id( $reply_id = 0 ) {
+		$bbp      = bbpress();
+		$wp_query = bbp_get_wp_query();
+
+		// Easy empty checking
+		if ( ! empty( $reply_id ) && is_numeric( $reply_id ) ) {
+			$bbp_reply_id = $reply_id;
+
+		// Currently inside a replies loop
+		} elseif ( ! empty( $bbp->reply_query->in_the_loop ) && isset( $bbp->reply_query->post->ID ) ) {
+			$bbp_reply_id = $bbp->reply_query->post->ID;
+
+		// Currently inside a search loop
+		} elseif ( ! empty( $bbp->search_query->in_the_loop ) && isset( $bbp->search_query->post->ID ) && bbp_is_reply( $bbp->search_query->post->ID ) ) {
+			$bbp_reply_id = $bbp->search_query->post->ID;
+
+		// Currently viewing a forum
+		} elseif ( ( bbp_is_single_reply() || bbp_is_reply_edit() ) && ! empty( $bbp->current_reply_id ) ) {
+			$bbp_reply_id = $bbp->current_reply_id;
+
+		// Currently viewing a reply
+		} elseif ( ( bbp_is_single_reply() || bbp_is_reply_edit() ) && isset( $wp_query->post->ID ) ) {
+			$bbp_reply_id = $wp_query->post->ID;
+
+		// Fallback
+		} else {
+			$bbp_reply_id = 0;
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_id', $bbp_reply_id, $reply_id );
+	}
+
+/**
+ * Gets a reply
+ *
+ * @since 2.0.0 bbPress (r2787)
+ *
+ * @param int|object $reply reply id or reply object
+ * @param string $output Optional. OBJECT, ARRAY_A, or ARRAY_N. Default = OBJECT
+ * @param string $filter Optional Sanitation filter. See {@link sanitize_post()}
+ * @return mixed Null if error or reply (in specified form) if success
+ */
+function bbp_get_reply( $reply, $output = OBJECT, $filter = 'raw' ) {
+
+	// Maybe get ID from empty or int
+	if ( empty( $reply ) || is_numeric( $reply ) ) {
+		$reply = bbp_get_reply_id( $reply );
+	}
+
+	// Bail if no post object
+	$reply = get_post( $reply, OBJECT, $filter );
+	if ( empty( $reply ) ) {
+		return $reply;
+	}
+
+	// Bail if not correct post type
+	if ( $reply->post_type !== bbp_get_reply_post_type() ) {
+		return null;
+	}
+
+	// Default return value is OBJECT
+	$retval = $reply;
+
+	// Array A
+	if ( $output === ARRAY_A ) {
+		$retval = get_object_vars( $reply );
+
+	// Array N
+	} elseif ( $output === ARRAY_N ) {
+		$retval = array_values( get_object_vars( $reply ) );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_reply', $retval, $reply, $output, $filter );
+}
+
+/**
+ * Output the link to the reply in the reply loop
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_permalink( $reply_id = 0 ) {
+	echo esc_url( bbp_get_reply_permalink( $reply_id ) );
+}
+	/**
+	 * Return the link to the reply
+	 *
+	 * @since 2.0.0 bbPress (r2553)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 *                        and reply id
+	 * @return string Permanent link to reply
+	 */
+	function bbp_get_reply_permalink( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_permalink', get_permalink( $reply_id ), $reply_id );
+	}
+
+/**
+ * Output the paginated url to the reply in the reply loop
+ *
+ * @since 2.0.0 bbPress (r2679)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_url( $reply_id = 0 ) {
+	echo esc_url( bbp_get_reply_url( $reply_id ) );
+}
+	/**
+	 * Return the paginated url to the reply in the reply loop
+	 *
+	 * @since 2.0.0 bbPress (r2679)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @param string $redirect_to Optional. Pass a redirect value for use with
+	 *                              shortcodes and other fun things.
+	 * @return string Link to reply relative to paginated topic
+	 */
+	function bbp_get_reply_url( $reply_id = 0, $redirect_to = '' ) {
+
+		// Set needed variables
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = 0;
+
+		// Juggle reply & topic IDs for unpretty URL formatting
+		if ( bbp_is_reply( $reply_id ) ) {
+			$topic_id = bbp_get_reply_topic_id( $reply_id );
+			$topic    = bbp_get_topic( $topic_id );
+		} elseif ( bbp_is_topic( $reply_id ) ) {
+			$topic_id = bbp_get_topic_id( $reply_id );
+			$topic    = bbp_get_topic( $topic_id );
+			$reply_id = $topic_id;
+		}
+
+		// Hierarchical reply page
+		if ( bbp_thread_replies() ) {
+			$reply_page = 1;
+
+		// Standard reply page
+		} else {
+			$reply_page = ceil( (int) bbp_get_reply_position( $reply_id, $topic_id ) / (int) bbp_get_replies_per_page() );
+		}
+
+		// Get links & URLS
+		$reply_hash = '#post-' . $reply_id;
+		$topic_link = bbp_get_topic_permalink( $topic_id, $redirect_to );
+		$topic_url  = remove_query_arg( 'view', $topic_link );
+
+		// Get vars needed to support pending topics with unpretty links
+		$has_slug   = ! empty( $topic ) ? $topic->post_name : '';
+		$pretty     = bbp_use_pretty_urls();
+		$published  = ! bbp_is_topic_pending( $topic_id );
+
+		// Don't include pagination if on first page
+		if ( 1 >= $reply_page ) {
+
+			// Pretty permalinks
+			if ( ! empty( $has_slug ) && ! empty( $pretty ) && ! empty( $published ) ) {
+				$url = user_trailingslashit( $topic_url ) . $reply_hash;
+
+			// Unpretty links
+			} else {
+				$url = $topic_url . $reply_hash;
+			}
+
+		// Include pagination
+		} else {
+
+			// Pretty permalinks
+			if ( ! empty( $has_slug ) && ! empty( $pretty ) && ! empty( $published ) ) {
+				$url = trailingslashit( $topic_url ) . trailingslashit( bbp_get_paged_slug() ) . $reply_page;
+				$url = user_trailingslashit( $url ) . $reply_hash;
+
+			// Unpretty links
+			} else {
+				$url = add_query_arg( 'paged', $reply_page, $topic_url ) . $reply_hash;
+			}
+		}
+
+		// Add topic view query arg back to end if it is set
+		if ( bbp_get_view_all() ) {
+			$url = bbp_add_view_all( $url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_url', $url, $reply_id, $redirect_to );
+	}
+
+/**
+ * Output the title of the reply
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_title( $reply_id = 0 ) {
+	echo bbp_get_reply_title( $reply_id );
+}
+
+	/**
+	 * Return the title of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2553)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Title of reply
+	 */
+	function bbp_get_reply_title( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$title    = get_the_title( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_title', $title, $reply_id );
+	}
+
+	/**
+	 * Get empty reply title fallback.
+	 *
+	 * @since 2.5.0 bbPress (r5177)
+	 *
+	 * @param string $post_title Required. Reply Title
+	 * @param int $post_id Required. Reply ID
+	 * @return string Title of reply
+	 */
+	function bbp_get_reply_title_fallback( $post_title = '', $post_id = 0 ) {
+
+		// Bail if title not empty, or post is not a reply
+		if ( ! empty( $post_title ) || ! bbp_is_reply( $post_id ) ) {
+			return $post_title;
+		}
+
+		// Get reply topic title.
+		$topic_title = bbp_get_reply_topic_title( $post_id );
+
+		// Get empty reply title fallback.
+		$reply_title = sprintf( esc_html__( 'Reply To: %s', 'bbpress' ), $topic_title );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_title_fallback', $reply_title, $post_id, $topic_title );
+	}
+
+/**
+ * Output the content of the reply
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param int $reply_id Optional. reply id
+ */
+function bbp_reply_content( $reply_id = 0 ) {
+	echo bbp_get_reply_content( $reply_id );
+}
+	/**
+	 * Return the content of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2780)
+	 *
+	 * @param int $reply_id Optional. reply id
+	 * @return string Content of the reply
+	 */
+	function bbp_get_reply_content( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Check if password is required
+		if ( post_password_required( $reply_id ) ) {
+			return get_the_password_form();
+		}
+
+		$content = get_post_field( 'post_content', $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_content', $content, $reply_id );
+	}
+
+/**
+ * Output the excerpt of the reply
+ *
+ * @since 2.0.0 bbPress (r2751)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param int $length Optional. Length of the excerpt. Defaults to 100 letters
+ */
+function bbp_reply_excerpt( $reply_id = 0, $length = 100 ) {
+	echo bbp_get_reply_excerpt( $reply_id, $length );
+}
+	/**
+	 * Return the excerpt of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2751)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @param int $length Optional. Length of the excerpt. Defaults to 100
+	 *                     letters
+	 * @return string Reply Excerpt
+	 */
+	function bbp_get_reply_excerpt( $reply_id = 0, $length = 100 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$length   = (int) $length;
+		$excerpt  = get_post_field( 'post_excerpt', $reply_id );
+
+		if ( empty( $excerpt ) ) {
+			$excerpt = bbp_get_reply_content( $reply_id );
+		}
+
+		$excerpt = trim ( strip_tags( $excerpt ) );
+
+		// Multibyte support
+		if ( function_exists( 'mb_strlen' ) ) {
+			$excerpt_length = mb_strlen( $excerpt );
+		} else {
+			$excerpt_length = strlen( $excerpt );
+		}
+
+		if ( ! empty( $length ) && ( $excerpt_length > $length ) ) {
+			$excerpt  = mb_substr( $excerpt, 0, $length - 1 );
+			$excerpt .= '&hellip;';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_excerpt', $excerpt, $reply_id, $length );
+	}
+
+/**
+ * Output the post date and time of a reply
+ *
+ * @since 2.2.0 bbPress (r4155)
+ *
+ * @param int $reply_id Optional. Reply id.
+ * @param bool $humanize Optional. Humanize output using time_since
+ * @param bool $gmt Optional. Use GMT
+ */
+function bbp_reply_post_date( $reply_id = 0, $humanize = false, $gmt = false ) {
+	echo bbp_get_reply_post_date( $reply_id, $humanize, $gmt );
+}
+	/**
+	 * Return the post date and time of a reply
+	 *
+	 * @since 2.2.0 bbPress (r4155)
+	 *
+	 * @param int $reply_id Optional. Reply id.
+	 * @param bool $humanize Optional. Humanize output using time_since
+	 * @param bool $gmt Optional. Use GMT
+	 * @return string
+	 */
+	function bbp_get_reply_post_date( $reply_id = 0, $humanize = false, $gmt = false ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// 4 days, 4 hours ago
+		if ( ! empty( $humanize ) ) {
+			$gmt_s  = ! empty( $gmt ) ? 'G' : 'U';
+			$date   = get_post_time( $gmt_s, $gmt, $reply_id );
+			$time   = false; // For filter below
+			$result = bbp_get_time_since( $date );
+
+		// August 4, 2012 at 2:37 pm
+		} else {
+			$date   = get_post_time( get_option( 'date_format' ), $gmt, $reply_id, true );
+			$time   = get_post_time( get_option( 'time_format' ), $gmt, $reply_id, true );
+			$result = sprintf( _x( '%1$s at %2$s', 'date at time', 'bbpress' ), $date, $time );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_post_date', $result, $reply_id, $humanize, $gmt, $date, $time );
+	}
+
+/**
+ * Append revisions to the reply content
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param string $content Optional. Content to which we need to append the revisions to
+ * @param int $reply_id Optional. Reply id
+ * @return string Content with the revisions appended
+ */
+function bbp_reply_content_append_revisions( $content = '', $reply_id = 0 ) {
+
+	// Bail if in admin or feed
+	if ( is_admin() || is_feed() ) {
+		return $content;
+	}
+
+	// Validate the ID
+	$reply_id = bbp_get_reply_id( $reply_id );
+
+	// Filter & return
+	return apply_filters( 'bbp_reply_append_revisions', $content . bbp_get_reply_revision_log( $reply_id ), $content, $reply_id );
+}
+
+/**
+ * Output the revision log of the reply
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_revision_log( $reply_id = 0 ) {
+	echo bbp_get_reply_revision_log( $reply_id );
+}
+	/**
+	 * Return the formatted revision log of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2782)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Revision log of the reply
+	 */
+	function bbp_get_reply_revision_log( $reply_id = 0 ) {
+
+		// Create necessary variables
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Show the topic reply log if this is a topic in a reply loop
+		if ( bbp_is_topic( $reply_id ) ) {
+			return bbp_get_topic_revision_log( $reply_id );
+		}
+
+		// Get the reply revision log (out of post meta
+		$revision_log = bbp_get_reply_raw_revision_log( $reply_id );
+
+		// Check reply and revision log exist
+		if ( empty( $reply_id ) || empty( $revision_log ) || ! is_array( $revision_log ) ) {
+			return false;
+		}
+
+		// Get the actual revisions
+		$revisions = bbp_get_reply_revisions( $reply_id );
+		if ( empty( $revisions ) ) {
+			return false;
+		}
+
+		$r = "\n\n" . '<ul id="bbp-reply-revision-log-' . esc_attr( $reply_id ) . '" class="bbp-reply-revision-log">' . "\n\n";
+
+		// Loop through revisions
+		foreach ( (array) $revisions as $revision ) {
+
+			if ( empty( $revision_log[ $revision->ID ] ) ) {
+				$author_id = $revision->post_author;
+				$reason    = '';
+			} else {
+				$author_id = $revision_log[ $revision->ID ]['author'];
+				$reason    = $revision_log[ $revision->ID ]['reason'];
+			}
+
+			$author = bbp_get_author_link( array( 'size' => 14, 'link_text' => bbp_get_reply_author_display_name( $revision->ID ), 'post_id' => $revision->ID ) );
+			$since  = bbp_get_time_since( bbp_convert_date( $revision->post_modified ) );
+
+			$r .= "\t" . '<li id="bbp-reply-revision-log-' . esc_attr( $reply_id ) . '-item-' . esc_attr( $revision->ID ) . '" class="bbp-reply-revision-log-item">' . "\n";
+			if ( ! empty( $reason ) ) {
+				$r .= "\t\t" . sprintf( esc_html__( 'This reply was modified %1$s by %2$s. Reason: %3$s', 'bbpress' ), esc_html( $since ), $author, esc_html( $reason ) ) . "\n";
+			} else {
+				$r .= "\t\t" . sprintf( esc_html__( 'This reply was modified %1$s by %2$s.', 'bbpress' ), esc_html( $since ), $author ) . "\n";
+			}
+			$r .= "\t" . '</li>' . "\n";
+
+		}
+
+		$r .= "\n" . '</ul>' . "\n\n";
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_revision_log', $r, $reply_id );
+	}
+		/**
+		 * Return the raw revision log of the reply
+		 *
+		 * @since 2.0.0 bbPress (r2782)
+		 *
+		 * @param int $reply_id Optional. Reply id
+		 * @return string Raw revision log of the reply
+		 */
+		function bbp_get_reply_raw_revision_log( $reply_id = 0 ) {
+			$reply_id     = bbp_get_reply_id( $reply_id );
+			$revision_log = get_post_meta( $reply_id, '_bbp_revision_log', true );
+			$revision_log = ! empty( $revision_log )
+				? $revision_log
+				: array();
+
+			// Filter & return
+			return apply_filters( 'bbp_get_reply_raw_revision_log', $revision_log, $reply_id );
+		}
+
+/**
+ * Return the revisions of the reply
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @return string reply revisions
+ */
+function bbp_get_reply_revisions( $reply_id = 0 ) {
+	$reply_id  = bbp_get_reply_id( $reply_id );
+	$revisions = wp_get_post_revisions( $reply_id, array( 'order' => 'ASC' ) );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_reply_revisions', $revisions, $reply_id );
+}
+
+/**
+ * Return the revision count of the reply
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param boolean $integer Optional. Whether or not to format the result
+ * @return string reply revision count
+ */
+function bbp_get_reply_revision_count( $reply_id = 0, $integer = false ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$count    = count( bbp_get_reply_revisions( $reply_id ) );
+	$filter   = ( true === $integer )
+		? 'bbp_get_reply_revision_count_int'
+		: 'bbp_get_reply_revision_count';
+
+	return apply_filters( $filter, $count, $reply_id );
+}
+
+/**
+ * Output the status of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_status( $reply_id = 0 ) {
+	echo bbp_get_reply_status( $reply_id );
+}
+	/**
+	 * Return the status of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Status of reply
+	 */
+	function bbp_get_reply_status( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_status', get_post_status( $reply_id ), $reply_id );
+	}
+
+/**
+ * Is the reply publicly viewable?
+ *
+ * See bbp_get_public_reply_statuses() for public statuses.
+ *
+ * @since 2.6.0 bbPress (r6391)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @return bool True if public, false if not.
+ */
+function bbp_is_reply_public( $reply_id = 0 ) {
+	$reply_id  = bbp_get_reply_id( $reply_id );
+	$status    = bbp_get_reply_status( $reply_id );
+	$public    = bbp_get_public_reply_statuses();
+	$is_public = in_array( $status, $public, true );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_public', $is_public, $reply_id );
+}
+
+/**
+ * Is the reply not spam or deleted?
+ *
+ * @since 2.0.0 bbPress (r3496)
+ * @since 2.6.0 bbPress (r6922) Returns false if topic is also not published
+ *
+ * @param int $reply_id Optional. Topic id
+ * @return bool True if published, false if not.
+ */
+function bbp_is_reply_published( $reply_id = 0 ) {
+	$reply_id     = bbp_get_reply_id( $reply_id );
+	$topic_id     = bbp_get_reply_topic_id( $reply_id );
+	$status       = bbp_get_public_status_id();
+	$topic_status = bbp_is_topic_published( $topic_id );
+	$reply_status = ( bbp_get_reply_status( $reply_id ) === $status );
+	$retval       = ( $reply_status && $topic_status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_published', (bool) $retval, $reply_id );
+}
+
+/**
+ * Is the reply marked as spam?
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @return bool True if spam, false if not.
+ */
+function bbp_is_reply_spam( $reply_id = 0 ) {
+	$reply_id     = bbp_get_reply_id( $reply_id );
+	$status       = bbp_get_spam_status_id();
+	$reply_status = bbp_get_reply_status( $reply_id ) === $status;
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_spam', (bool) $reply_status, $reply_id );
+}
+
+/**
+ * Is the reply trashed?
+ *
+ * @since 2.0.0 bbPress (r2884)
+ *
+ * @param int $reply_id Optional. Topic id
+ * @return bool True if spam, false if not.
+ */
+function bbp_is_reply_trash( $reply_id = 0 ) {
+	$reply_id     = bbp_get_reply_id( $reply_id );
+	$status       = bbp_get_trash_status_id();
+	$reply_status = bbp_get_reply_status( $reply_id ) === $status;
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_trash', (bool) $reply_status, $reply_id );
+}
+
+/**
+ * Is the reply pending?
+ *
+ * @since 2.6.0 bbPress (r5507)
+ *
+ * @param int $reply_id Optional. Topic id
+ * @return bool True if pending, false if not.
+ */
+function bbp_is_reply_pending( $reply_id = 0 ) {
+	$reply_id     = bbp_get_reply_id( $reply_id );
+	$status       = bbp_get_pending_status_id();
+	$reply_status = bbp_get_reply_status( $reply_id ) === $status;
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_pending', (bool) $reply_status, $reply_id );
+}
+
+/**
+ * Is the reply private?
+ *
+ * @since 2.6.0 bbPress (r5507)
+ *
+ * @param int $reply_id Optional. Topic id
+ * @return bool True if private, false if not.
+ */
+function bbp_is_reply_private( $reply_id = 0 ) {
+	$reply_id     = bbp_get_reply_id( $reply_id );
+	$status       = bbp_get_private_status_id();
+	$reply_status = bbp_get_reply_status( $reply_id ) === $status;
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_private', (bool) $reply_status, $reply_id );
+}
+
+/**
+ * Is the reply by an anonymous user?
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @return bool True if the post is by an anonymous user, false if not.
+ */
+function bbp_is_reply_anonymous( $reply_id = 0 ) {
+	$reply_id = bbp_get_reply_id( $reply_id );
+	$retval   = false;
+
+	if ( ! bbp_get_reply_author_id( $reply_id ) ) {
+		$retval = true;
+
+	} elseif ( get_post_meta( $reply_id, '_bbp_anonymous_name', true ) ) {
+		$retval = true;
+
+	} elseif ( get_post_meta( $reply_id, '_bbp_anonymous_email', true ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_reply_anonymous', $retval, $reply_id );
+}
+
+/**
+ * Deprecated. Use bbp_reply_author_display_name() instead.
+ *
+ * Output the author of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @deprecated 2.5.0 bbPress (r5119)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_author( $reply_id = 0 ) {
+	echo bbp_get_reply_author( $reply_id );
+}
+	/**
+	 * Deprecated. Use bbp_get_reply_author_display_name() instead.
+	 *
+	 * Return the author of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @deprecated 2.5.0 bbPress (r5119)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Author of reply
+	 */
+	function bbp_get_reply_author( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		if ( ! bbp_is_reply_anonymous( $reply_id ) ) {
+			$author = get_the_author_meta( 'display_name', bbp_get_reply_author_id( $reply_id ) );
+		} else {
+			$author = get_post_meta( $reply_id, '_bbp_anonymous_name', true );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author', $author, $reply_id );
+	}
+
+/**
+ * Output the author ID of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_author_id( $reply_id = 0 ) {
+	echo bbp_get_reply_author_id( $reply_id );
+}
+	/**
+	 * Return the author ID of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Author id of reply
+	 */
+	function bbp_get_reply_author_id( $reply_id = 0 ) {
+		$reply_id  = bbp_get_reply_id( $reply_id );
+		$author_id = get_post_field( 'post_author', $reply_id );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_author_id', $author_id, $reply_id );
+	}
+
+/**
+ * Output the author display_name of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_author_display_name( $reply_id = 0 ) {
+	echo bbp_get_reply_author_display_name( $reply_id );
+}
+	/**
+	 * Return the author display_name of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string The display name of the author of the reply
+	 */
+	function bbp_get_reply_author_display_name( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// User is not a guest
+		if ( ! bbp_is_reply_anonymous( $reply_id ) ) {
+
+			// Get the author ID
+			$author_id = bbp_get_reply_author_id( $reply_id );
+
+			// Try to get a display name
+			$author_name = get_the_author_meta( 'display_name', $author_id );
+
+			// Fall back to user login
+			if ( empty( $author_name ) ) {
+				$author_name = get_the_author_meta( 'user_login', $author_id );
+			}
+
+		// User does not have an account
+		} else {
+			$author_name = get_post_meta( $reply_id, '_bbp_anonymous_name', true );
+		}
+
+		// Fallback if nothing could be found
+		if ( empty( $author_name ) ) {
+			$author_name = bbp_get_fallback_display_name( $reply_id );
+		}
+
+		// Encode possible UTF8 display names
+		if ( seems_utf8( $author_name ) === false ) {
+			$author_name = utf8_encode( $author_name );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_display_name', $author_name, $reply_id );
+	}
+
+/**
+ * Output the author avatar of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param int $size Optional. Size of the avatar. Defaults to 40
+ */
+function bbp_reply_author_avatar( $reply_id = 0, $size = 40 ) {
+	echo bbp_get_reply_author_avatar( $reply_id, $size );
+}
+	/**
+	 * Return the author avatar of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @param int $size Optional. Size of the avatar. Defaults to 40
+	 * @return string Avatar of author of the reply
+	 */
+	function bbp_get_reply_author_avatar( $reply_id = 0, $size = 40 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		if ( ! empty( $reply_id ) ) {
+			// Check for anonymous user
+			if ( ! bbp_is_reply_anonymous( $reply_id ) ) {
+				$author_avatar = get_avatar( bbp_get_reply_author_id( $reply_id ), $size );
+			} else {
+				$author_avatar = get_avatar( get_post_meta( $reply_id, '_bbp_anonymous_email', true ), $size );
+			}
+		} else {
+			$author_avatar = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_avatar', $author_avatar, $reply_id, $size );
+	}
+
+/**
+ * Output the author link of the reply
+ *
+ * @since 2.0.0 bbPress (r2717)
+ *
+ * @param array $args Optional. If it is an integer, it is used as reply id.
+ */
+function bbp_reply_author_link( $args = array() ) {
+	echo bbp_get_reply_author_link( $args );
+}
+	/**
+	 * Return the author link of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2717)
+	 *
+	 * @param array $args Optional. If an integer, it is used as reply id.
+	 * @return string Author link of reply
+	 */
+	function bbp_get_reply_author_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'post_id'    => 0,
+			'link_title' => '',
+			'type'       => 'both',
+			'size'       => 80,
+			'sep'        => '',
+			'show_role'  => false
+		), 'get_reply_author_link' );
+
+		// Default return value
+		$author_link = '';
+
+		// Used as reply_id
+		$reply_id = is_numeric( $args )
+			? bbp_get_reply_id( $args )
+			: bbp_get_reply_id( $r['post_id'] );
+
+		// Reply ID is good
+		if ( ! empty( $reply_id ) ) {
+
+			// Get some useful reply information
+			$author_url = bbp_get_reply_author_url( $reply_id );
+			$anonymous  = bbp_is_reply_anonymous( $reply_id );
+
+			// Tweak link title if empty
+			if ( empty( $r['link_title'] ) ) {
+				$author = bbp_get_reply_author_display_name( $reply_id );
+				$title  = empty( $anonymous )
+					? esc_attr__( "View %s's profile",  'bbpress' )
+					: esc_attr__( "Visit %s's website", 'bbpress' );
+
+				$link_title = sprintf( $title, $author );
+
+			// Use what was passed if not
+			} else {
+				$link_title = $r['link_title'];
+			}
+
+			// Setup title and author_links array
+			$author_links = array();
+			$link_title   = ! empty( $link_title )
+				? ' title="' . esc_attr( $link_title ) . '"'
+				: '';
+
+			// Get avatar (unescaped, because HTML)
+			if ( ( 'avatar' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['avatar'] = bbp_get_reply_author_avatar( $reply_id, $r['size'] );
+			}
+
+			// Get display name (escaped, because never HTML)
+			if ( ( 'name' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['name'] = esc_html( bbp_get_reply_author_display_name( $reply_id ) );
+			}
+
+			// Empty array
+			$links  = array();
+			$sprint = '<span %1$s>%2$s</span>';
+
+			// Wrap each link
+			foreach ( $author_links as $link => $link_text ) {
+				$link_class = ' class="bbp-author-' . esc_attr( $link ) . '"';
+				$links[]    = sprintf( $sprint, $link_class, $link_text );
+			}
+
+			// Juggle
+			$author_links = $links;
+			unset( $links );
+
+			// Filter sections
+			$sections    = apply_filters( 'bbp_get_reply_author_links', $author_links, $r, $args );
+
+			// Assemble sections into author link
+			$author_link = implode( $r['sep'], $sections );
+
+			// Only wrap in link if profile exists
+			if ( empty( $anonymous ) && bbp_user_has_profile( bbp_get_reply_author_id( $reply_id ) ) ) {
+				$author_link = sprintf( '<a href="%1$s"%2$s%3$s>%4$s</a>', esc_url( $author_url ), $link_title, ' class="bbp-author-link"', $author_link );
+			}
+
+			// Role is not linked
+			if ( true === $r['show_role'] ) {
+				$author_link .= bbp_get_reply_author_role( array( 'reply_id' => $reply_id ) );
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_link', $author_link, $r, $args );
+	}
+
+/**
+ * Output the author url of the reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_author_url( $reply_id = 0 ) {
+	echo esc_url( bbp_get_reply_author_url( $reply_id ) );
+}
+	/**
+	 * Return the author url of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Author URL of the reply
+	 */
+	function bbp_get_reply_author_url( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Check for anonymous user or non-existant user
+		if ( ! bbp_is_reply_anonymous( $reply_id ) && bbp_user_has_profile( bbp_get_reply_author_id( $reply_id ) ) ) {
+			$author_url = bbp_get_user_profile_url( bbp_get_reply_author_id( $reply_id ) );
+		} else {
+			$author_url = get_post_meta( $reply_id, '_bbp_anonymous_website', true );
+			if ( empty( $author_url ) ) {
+				$author_url = '';
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_url', $author_url, $reply_id );
+	}
+
+/**
+ * Output the reply author email address
+ *
+ * @since 2.0.0 bbPress (r3445)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_author_email( $reply_id = 0 ) {
+	echo bbp_get_reply_author_email( $reply_id );
+}
+	/**
+	 * Return the reply author email address
+	 *
+	 * @since 2.0.0 bbPress (r3445)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Reply author email address
+	 */
+	function bbp_get_reply_author_email( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Not anonymous
+		if ( ! bbp_is_reply_anonymous( $reply_id ) ) {
+
+			// Use reply author email address
+			$user_id      = bbp_get_reply_author_id( $reply_id );
+			$user         = get_userdata( $user_id );
+			$author_email = ! empty( $user->user_email ) ? $user->user_email : '';
+
+		// Anonymous
+		} else {
+
+			// Get email from post meta
+			$author_email = get_post_meta( $reply_id, '_bbp_anonymous_email', true );
+
+			// Sanity check for missing email address
+			if ( empty( $author_email ) ) {
+				$author_email = '';
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_email', $author_email, $reply_id );
+	}
+
+/**
+ * Output the reply author role
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @param array $args Optional.
+ */
+function bbp_reply_author_role( $args = array() ) {
+	echo bbp_get_reply_author_role( $args );
+}
+	/**
+	 * Return the reply author role
+	 *
+	 * @since 2.1.0 bbPress (r3860)
+	 *
+	 * @param array $args Optional.
+	 * @return string Reply author role
+	 */
+	function bbp_get_reply_author_role( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'reply_id' => 0,
+			'class'    => false,
+			'before'   => '<div class="bbp-author-role">',
+			'after'    => '</div>'
+		), 'get_reply_author_role' );
+
+		$reply_id    = bbp_get_reply_id( $r['reply_id'] );
+		$role        = bbp_get_user_display_role( bbp_get_reply_author_id( $reply_id ) );
+
+		// Backwards compatibilty with old 'class' argument
+		if ( ! empty( $r['class'] ) ) {
+			$author_role = sprintf( '%1$s<div class="%2$s">%3$s</div>%4$s', $r['before'], esc_attr( $r['class'] ), esc_html( $role ), $r['after'] );
+
+		// Simpler before & after arguments
+		// https://bbpress.trac.wordpress.org/ticket/2557
+		} else {
+			$author_role = $r['before'] . $role . $r['after'];
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_author_role', $author_role, $r, $args );
+	}
+
+/**
+ * Output the topic title a reply belongs to
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_topic_title( $reply_id = 0 ) {
+	echo bbp_get_reply_topic_title( $reply_id );
+}
+	/**
+	 * Return the topic title a reply belongs to
+	 *
+	 * @since 2.0.0 bbPress (r2553)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string The topic title of the reply
+	 */
+	function bbp_get_reply_topic_title( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_topic_title', bbp_get_topic_title( $topic_id ), $reply_id );
+	}
+
+/**
+ * Output the topic id a reply belongs to
+ *
+ * @since 2.0.0 bbPress (r2553)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_topic_id( $reply_id = 0 ) {
+	echo bbp_get_reply_topic_id( $reply_id );
+}
+	/**
+	 * Return the topic id a reply belongs to
+	 *
+	 * @since 2.0.0 bbPress (r2553)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return int The topic id of the reply
+	 */
+	function bbp_get_reply_topic_id( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = (int) get_post_field( 'post_parent', $reply_id );
+
+		// Meta-data fallback
+		if ( empty( $topic_id ) ) {
+			$topic_id = (int) get_post_meta( $reply_id, '_bbp_topic_id', true );
+		}
+
+		// Filter
+		if ( ! empty( $topic_id ) ) {
+			$topic_id = (int) bbp_get_topic_id( $topic_id );
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_topic_id', $topic_id, $reply_id );
+	}
+
+/**
+ * Output the forum id a reply belongs to
+ *
+ * @since 2.0.0 bbPress (r2679)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_forum_id( $reply_id = 0 ) {
+	echo bbp_get_reply_forum_id( $reply_id );
+}
+	/**
+	 * Return the forum id a reply belongs to
+	 *
+	 * @since 2.0.0 bbPress (r2679)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 *                        id and reply id
+	 * @return int The forum id of the reply
+	 */
+	function bbp_get_reply_forum_id( $reply_id = 0 ) {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+		$forum_id = (int) get_post_field( 'post_parent', $topic_id );
+
+		// Meta-data fallback
+		if ( empty( $forum_id ) ) {
+			$forum_id = (int) get_post_meta( $reply_id, '_bbp_forum_id', true );
+		}
+
+		// Filter
+		if ( ! empty( $forum_id ) ) {
+			$forum_id = (int) bbp_get_forum_id( $forum_id );
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_forum_id', $forum_id, $reply_id );
+	}
+
+/**
+ * Output the ancestor reply id of a reply
+ *
+ * @since 2.4.0 bbPress (r4944)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_ancestor_id( $reply_id = 0 ) {
+	echo bbp_get_reply_ancestor_id( $reply_id );
+}
+	/**
+	 * Return the ancestor reply id of a reply
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param in $reply_id Reply id
+	 */
+	function bbp_get_reply_ancestor_id( $reply_id = 0 ) {
+
+		// Validation
+		$reply_id = bbp_get_reply_id( $reply_id );
+		if ( empty( $reply_id ) ) {
+			return false;
+		}
+
+		// Find highest reply ancestor
+		$ancestor_id = $reply_id;
+		while ( $parent_id = bbp_get_reply_to( $ancestor_id ) ) {
+			if ( empty( $parent_id ) || ( $parent_id === $ancestor_id ) || ( bbp_get_reply_topic_id( $reply_id ) === $parent_id ) || ( $parent_id === $reply_id ) ) {
+				break;
+			}
+			$ancestor_id = $parent_id;
+		}
+
+		return (int) $ancestor_id;
+	}
+
+/**
+ * Output the reply to id of a reply
+ *
+ * @since 2.4.0 bbPress (r4944)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_to( $reply_id = 0 ) {
+	echo bbp_get_reply_to( $reply_id );
+}
+	/**
+	 * Return the reply to id of a reply
+	 *
+ 	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return int The parent reply id of the reply
+	 */
+	function bbp_get_reply_to( $reply_id = 0 ) {
+
+		// Assume there is no reply_to set
+		$reply_to = 0;
+
+		// Check that reply_id is valid
+		$reply_id = bbp_get_reply_id( $reply_id );
+
+		// Get reply_to value
+		if ( ! empty( $reply_id ) ) {
+			$reply_to = (int) get_post_meta( $reply_id, '_bbp_reply_to', true );
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_to', $reply_to, $reply_id );
+	}
+
+/**
+ * Output the link for the reply to
+ *
+ * @since 2.4.0 bbPress (r4944)
+ *
+ * @param array $args
+ */
+function bbp_reply_to_link( $args = array() ) {
+	echo bbp_get_reply_to_link( $args );
+}
+
+	/**
+	 * Return the link for a reply to a reply
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param array $args Arguments
+	 * @return string Link for a reply to a reply
+	 */
+	function bbp_get_reply_to_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'reply_text'   => esc_html_x( 'Reply', 'verb', 'bbpress' ),
+			'depth'        => 0,
+			'add_below'    => 'post',
+			'respond_id'   => 'new-reply-' . bbp_get_topic_id(),
+		), 'get_reply_to_link' );
+
+		// Get the reply to use it's ID and post_parent
+		$reply    = bbp_get_reply( $r['id'] );
+		$topic_id = bbp_get_reply_topic_id( $reply->ID );
+
+		// Bail if no reply or user cannot reply
+		if ( empty( $reply ) || empty( $topic_id ) || bbp_is_single_reply() || ! bbp_current_user_can_access_create_reply_form() ) {
+			return;
+		}
+
+		// Build the URI and return value
+		$uri = remove_query_arg( array( 'bbp_reply_to' ) );
+		$uri = add_query_arg( array( 'bbp_reply_to' => $reply->ID ) );
+		$uri = wp_nonce_url( $uri, 'respond_id_' . $reply->ID );
+		$uri = $uri . '#new-post';
+
+		// Only add onclick if replies are threaded
+		if ( bbp_thread_replies() ) {
+
+			// Array of classes to pass to moveForm
+			$move_form = array(
+				$r['add_below'] . '-' . $reply->ID,
+				$reply->ID,
+				$r['respond_id'],
+				$reply->post_parent
+			);
+
+			// Build the onclick
+			$onclick  = ' onclick="return addReply.moveForm(\'' . implode( "','", $move_form ) . '\');"';
+
+		// No onclick if replies are not threaded
+		} else {
+			$onclick  = '';
+		}
+
+		// Add $uri to the array, to be passed through the filter
+		$r['uri'] = $uri;
+		$retval   = $r['link_before'] . '<a role="button" href="' . esc_url( $r['uri'] ) . '" class="bbp-reply-to-link"' . $onclick . '>' . $r['reply_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_to_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the reply to a reply cancellation link
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+function bbp_cancel_reply_to_link( $text = '' ) {
+	echo bbp_get_cancel_reply_to_link( $text );
+}
+	/**
+	 * Return the cancellation link for a reply to a reply
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @param string $text The cancel text
+	 * @return string The cancellation link
+	 */
+	function bbp_get_cancel_reply_to_link( $text = '' ) {
+
+		// Bail if not hierarchical or editing a reply
+		if ( ! bbp_thread_replies() || bbp_is_reply_edit() ) {
+			return;
+		}
+
+		// Set default text
+		if ( empty( $text ) ) {
+			$text = esc_html__( 'Cancel', 'bbpress' );
+		}
+
+		// Replying to...
+		$reply_to = isset( $_GET['bbp_reply_to'] )
+			? (int) $_GET['bbp_reply_to']
+			: 0;
+
+		// Set visibility
+		$style  = ! empty( $reply_to ) ? '' : ' style="display:none;"';
+		$link   = remove_query_arg( array( 'bbp_reply_to', '_wpnonce' ) ) . "#post-{$reply_to}";
+		$retval = sprintf( '<a href="%1$s" id="bbp-cancel-reply-to-link"%2$s>%3$s</a>', esc_url( $link ), $style, esc_html( $text ) );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_cancel_reply_to_link', $retval, $link, $text );
+	}
+
+/**
+ * Output the numeric position of a reply within a topic
+ *
+ * @since 2.0.0 bbPress (r2984)
+ *
+ * @param int $reply_id Optional. Reply id
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_reply_position( $reply_id = 0, $topic_id = 0 ) {
+	echo bbp_get_reply_position( $reply_id, $topic_id );
+}
+	/**
+	 * Return the numeric position of a reply within a topic
+	 *
+	 * @since 2.0.0 bbPress (r2984)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @param int $topic_id Optional. Topic id
+	 * @return int Reply position
+	 */
+	function bbp_get_reply_position( $reply_id = 0, $topic_id = 0 ) {
+
+		// Get required data
+		$reply_id       = bbp_get_reply_id( $reply_id );
+		$reply_position = get_post_field( 'menu_order', $reply_id );
+
+		// Reply doesn't have a position so get the raw value
+		if ( empty( $reply_position ) ) {
+
+			// Get topic ID
+			$topic_id = ! empty( $topic_id )
+				? bbp_get_topic_id( $topic_id )
+				: bbp_get_reply_topic_id( $reply_id );
+
+			// Post is not the topic
+			if ( $reply_id !== $topic_id ) {
+				$reply_position = bbp_get_reply_position_raw( $reply_id, $topic_id );
+
+				// Update the reply position in the posts table so we'll never have
+				// to hit the DB again.
+				if ( ! empty( $reply_position ) ) {
+					bbp_update_reply_position( $reply_id, $reply_position );
+				}
+
+			// Topic's position is always 0
+			} else {
+				$reply_position = 0;
+			}
+		}
+
+		// Bump the position by one if the topic is included in the reply loop
+		if ( ! bbp_show_lead_topic() ) {
+			$reply_position++;
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_reply_position', $reply_position, $reply_id, $topic_id );
+	}
+
+/** Reply Admin Links *********************************************************/
+
+/**
+ * Output admin links for reply
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param array $args See {@link bbp_get_reply_admin_links()}
+ */
+function bbp_reply_admin_links( $args = array() ) {
+	echo bbp_get_reply_admin_links( $args );
+}
+	/**
+	 * Return admin links for reply
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Optional. Reply id
+	 *  - before: HTML before the links. Defaults to
+	 *             '<span class="bbp-admin-links">'
+	 *  - after: HTML after the links. Defaults to '</span>'
+	 *  - sep: Separator. Defaults to ' | '
+	 *  - links: Array of the links to display. By default, edit, trash,
+	 *            spam, reply move, and topic split links are displayed
+	 * @return string Reply admin links
+	 */
+	function bbp_get_reply_admin_links( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'     => 0,
+			'before' => '<span class="bbp-admin-links">',
+			'after'  => '</span>',
+			'sep'    => ' | ',
+			'links'  => array()
+		), 'get_reply_admin_links' );
+
+		$r['id'] = bbp_get_reply_id( $r['id'] );
+
+		// If post is a topic, return the topic admin links instead
+		if ( bbp_is_topic( $r['id'] ) ) {
+			return bbp_get_topic_admin_links( $args );
+		}
+
+		// If post is not a reply, return
+		if ( ! bbp_is_reply( $r['id'] ) ) {
+			return;
+		}
+
+		// If topic is trashed, do not show admin links
+		if ( bbp_is_topic_trash( bbp_get_reply_topic_id( $r['id'] ) ) ) {
+			return;
+		}
+
+		// If no links were passed, default to the standard
+		if ( empty( $r['links'] ) ) {
+			$r['links'] = apply_filters( 'bbp_reply_admin_links', array(
+				'edit'    => bbp_get_reply_edit_link   ( $r ),
+				'move'    => bbp_get_reply_move_link   ( $r ),
+				'split'   => bbp_get_topic_split_link  ( $r ),
+				'trash'   => bbp_get_reply_trash_link  ( $r ),
+				'spam'    => bbp_get_reply_spam_link   ( $r ),
+				'approve' => bbp_get_reply_approve_link( $r ),
+				'reply'   => bbp_get_reply_to_link     ( $r )
+			), $r['id'] );
+		}
+
+		// See if links need to be unset
+		$reply_status = bbp_get_reply_status( $r['id'] );
+		if ( in_array( $reply_status, array( bbp_get_spam_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id() ), true ) ) {
+
+			// Spam link shouldn't be visible on trashed topics
+			if ( bbp_get_trash_status_id() === $reply_status ) {
+				unset( $r['links']['spam'] );
+
+			// Trash link shouldn't be visible on spam topics
+			} elseif ( bbp_get_spam_status_id() === $reply_status ) {
+				unset( $r['links']['trash'] );
+			}
+		}
+
+		// Process the admin links
+		$links  = implode( $r['sep'], array_filter( $r['links'] ) );
+		$retval = $r['before'] . $links . $r['after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_admin_links', $retval, $r, $args );
+	}
+
+/**
+ * Output the edit link of the reply
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param array $args See {@link bbp_get_reply_edit_link()}
+ */
+function bbp_reply_edit_link( $args = array() ) {
+	echo bbp_get_reply_edit_link( $args );
+}
+
+	/**
+	 * Return the edit link of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Reply id
+	 *  - link_before: HTML before the link
+	 *  - link_after: HTML after the link
+	 *  - edit_text: Edit text. Defaults to 'Edit'
+	 * @return string Reply edit link
+	 */
+	function bbp_get_reply_edit_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'edit_text'    => esc_html__( 'Edit', 'bbpress' )
+		), 'get_reply_edit_link' );
+
+		// Get reply
+		$reply = bbp_get_reply( $r['id'] );
+
+		// Bypass check if user has caps
+		if ( ! current_user_can( 'edit_others_replies' ) ) {
+
+			// User cannot edit or it is past the lock time
+			if ( empty( $reply ) || ! current_user_can( 'edit_reply', $reply->ID ) || bbp_past_edit_lock( $reply->post_date_gmt ) ) {
+				return;
+			}
+		}
+
+		// Get uri
+		$uri = bbp_get_reply_edit_url( $r['id'] );
+
+		// Bail if no uri
+		if ( empty( $uri ) ) {
+			return;
+		}
+
+		$retval = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-reply-edit-link">' . $r['edit_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_edit_link', $retval, $r, $args );
+	}
+
+/**
+ * Output URL to the reply edit page
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @param int $reply_id Optional. Reply id
+ */
+function bbp_reply_edit_url( $reply_id = 0 ) {
+	echo esc_url( bbp_get_reply_edit_url( $reply_id ) );
+}
+	/**
+	 * Return URL to the reply edit page
+	 *
+	 * @since 2.0.0 bbPress (r2753)
+	 *
+	 * @param int $reply_id Optional. Reply id
+	 * @return string Reply edit url
+	 */
+	function bbp_get_reply_edit_url( $reply_id = 0 ) {
+
+		// Bail if no reply
+		$reply = bbp_get_reply( $reply_id );
+		if ( empty( $reply ) ) {
+			return;
+		}
+
+		$reply_link = bbp_remove_view_all( bbp_get_reply_permalink( $reply_id ) );
+
+		// Pretty permalinks, previously used `bbp_use_pretty_urls()`
+		// https://bbpress.trac.wordpress.org/ticket/3054
+		if ( false === strpos( $reply_link, '?' ) ) {
+			$url = trailingslashit( $reply_link ) . bbp_get_edit_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_reply_post_type() => $reply->post_name,
+				bbp_get_edit_rewrite_id() => '1'
+			), $reply_link );
+		}
+
+		// Maybe add view all
+		$url = bbp_add_view_all( $url );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_edit_url', $url, $reply_id );
+	}
+
+/**
+ * Output the trash link of the reply
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param array $args See {@link bbp_get_reply_trash_link()}
+ */
+function bbp_reply_trash_link( $args = array() ) {
+	echo bbp_get_reply_trash_link( $args );
+}
+
+	/**
+	 * Return the trash link of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Reply id
+	 *  - link_before: HTML before the link
+	 *  - link_after: HTML after the link
+	 *  - sep: Separator
+	 *  - trash_text: Trash text
+	 *  - restore_text: Restore text
+	 *  - delete_text: Delete text
+	 * @return string Reply trash link
+	 */
+	function bbp_get_reply_trash_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'sep'          => ' | ',
+			'trash_text'   => esc_html__( 'Trash',   'bbpress' ),
+			'restore_text' => esc_html__( 'Restore', 'bbpress' ),
+			'delete_text'  => esc_html__( 'Delete',  'bbpress' )
+		), 'get_reply_trash_link' );
+
+		// Get reply
+		$reply = bbp_get_reply( $r['id'] );
+
+		// Bail if no reply or current user cannot delete
+		if ( empty( $reply ) || ! current_user_can( 'delete_reply', $reply->ID ) ) {
+			return;
+		}
+
+		$actions    = array();
+		$trash_days = bbp_get_trash_days( bbp_get_reply_post_type() );
+
+		// Trashed
+		if ( bbp_is_reply_trash( $reply->ID ) ) {
+			$actions['untrash'] = '<a title="' . esc_attr__( 'Restore this item from the Trash', 'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_reply_trash', 'sub_action' => 'untrash', 'reply_id' => $reply->ID ) ), 'untrash-' . $reply->post_type . '_' . $reply->ID ) ) . '" class="bbp-reply-restore-link">' . $r['restore_text'] . '</a>';
+
+		// Trash
+		} elseif ( ! empty( $trash_days ) ) {
+			$actions['trash']   = '<a title="' . esc_attr__( 'Move this item to the Trash',      'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_reply_trash', 'sub_action' => 'trash',   'reply_id' => $reply->ID ) ), 'trash-'   . $reply->post_type . '_' . $reply->ID ) ) . '" class="bbp-reply-trash-link">'   . $r['trash_text']   . '</a>';
+		}
+
+		// No trash
+		if ( bbp_is_reply_trash( $reply->ID ) || empty( $trash_days ) ) {
+			$actions['delete']  = '<a title="' . esc_attr__( 'Delete this item permanently',     'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_reply_trash', 'sub_action' => 'delete',  'reply_id' => $reply->ID ) ), 'delete-'  . $reply->post_type . '_' . $reply->ID ) ) . '" onclick="return confirm(\'' . esc_js( __( 'Are you sure you want to delete that permanently?', 'bbpress' ) ) . '\' );" class="bbp-reply-delete-link">' . $r['delete_text'] . '</a>';
+		}
+
+		// Process the admin links
+		$retval = $r['link_before'] . implode( $r['sep'], $actions ) . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_trash_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the spam link of the reply
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param array $args See {@link bbp_get_reply_spam_link()}
+ */
+function bbp_reply_spam_link( $args = array() ) {
+	echo bbp_get_reply_spam_link( $args );
+}
+
+	/**
+	 * Return the spam link of the reply
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Reply id
+	 *  - link_before: HTML before the link
+	 *  - link_after: HTML after the link
+	 *  - spam_text: Spam text
+	 *  - unspam_text: Unspam text
+	 * @return string Reply spam link
+	 */
+	function bbp_get_reply_spam_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'spam_text'    => esc_html__( 'Spam',   'bbpress' ),
+			'unspam_text'  => esc_html__( 'Unspam', 'bbpress' )
+		), 'get_reply_spam_link' );
+
+		// Get reply
+		$reply = bbp_get_reply( $r['id'] );
+
+		// Bail if no reply or current user cannot moderate
+		if ( empty( $reply ) || ! current_user_can( 'moderate', $reply->ID ) ) {
+			return;
+		}
+
+		$display = bbp_is_reply_spam( $reply->ID ) ? $r['unspam_text'] : $r['spam_text'];
+		$uri     = add_query_arg( array( 'action' => 'bbp_toggle_reply_spam', 'reply_id' => $reply->ID ) );
+		$uri     = wp_nonce_url( $uri, 'spam-reply_' . $reply->ID );
+		$retval  = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-reply-spam-link">' . $display . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_spam_link', $retval, $r, $args );
+	}
+
+/**
+ * Move reply link
+ *
+ * Output the move link of the reply
+ *
+ * @since 2.3.0 bbPress (r4521)
+ *
+ * @param array $args See {@link bbp_get_reply_move_link()}
+ */
+function bbp_reply_move_link( $args = array() ) {
+	echo bbp_get_reply_move_link( $args );
+}
+
+	/**
+	 * Get move reply link
+	 *
+	 * Return the move link of the reply
+	 *
+	 * @since 2.3.0 bbPress (r4521)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Reply id
+	 *  - link_before: HTML before the link
+	 *  - link_after: HTML after the link
+	 *  - move_text: Move text
+	 *  - move_title: Move title attribute
+	 * @return string Reply move link
+	 */
+	function bbp_get_reply_move_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'          => 0,
+			'link_before' => '',
+			'link_after'  => '',
+			'split_text'  => esc_html__( 'Move',            'bbpress' ),
+			'split_title' => esc_attr__( 'Move this reply', 'bbpress' )
+		), 'get_reply_move_link' );
+
+		// Get IDs
+		$reply_id = bbp_get_reply_id( $r['id'] );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Bail if no reply ID or user cannot moderate
+		if ( empty( $reply_id ) || ! current_user_can( 'moderate', $topic_id ) ) {
+			return;
+		}
+
+		$uri = add_query_arg( array(
+			'action'   => 'move',
+			'reply_id' => $reply_id
+		), bbp_get_reply_edit_url( $reply_id ) );
+
+		$retval = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" title="' . $r['split_title'] . '" class="bbp-reply-move-link">' . $r['split_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_move_link', $retval, $r, $args );
+	}
+
+/**
+ * Split topic link
+ *
+ * Output the split link of the topic (but is bundled with each reply)
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param array $args See {@link bbp_get_topic_split_link()}
+ */
+function bbp_topic_split_link( $args = array() ) {
+	echo bbp_get_topic_split_link( $args );
+}
+
+	/**
+	 * Get split topic link
+	 *
+	 * Return the split link of the topic (but is bundled with each reply)
+	 *
+	 * @since 2.0.0 bbPress (r2756)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Reply id
+	 *  - link_before: HTML before the link
+	 *  - link_after: HTML after the link
+	 *  - split_text: Split text
+	 *  - split_title: Split title attribute
+	 * @return string Topic split link
+	 */
+	function bbp_get_topic_split_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'          => 0,
+			'link_before' => '',
+			'link_after'  => '',
+			'split_text'  => esc_html__( 'Split',                           'bbpress' ),
+			'split_title' => esc_attr__( 'Split the topic from this reply', 'bbpress' )
+		), 'get_topic_split_link' );
+
+		// Get IDs
+		$reply_id = bbp_get_reply_id( $r['id'] );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Bail if no reply/topic ID, or user cannot moderate
+		if ( empty( $reply_id ) || empty( $topic_id ) || ! current_user_can( 'moderate', $topic_id ) ) {
+			return;
+		}
+
+		$uri = add_query_arg( array(
+			'action'   => 'split',
+			'reply_id' => $reply_id
+		), bbp_get_topic_edit_url( $topic_id ) );
+
+		$retval = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" title="' . $r['split_title'] . '" class="bbp-topic-split-link">' . $r['split_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_split_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the approve link of the reply
+ *
+ * @since 2.6.0 bbPress (r5507)
+ *
+ * @param array $args See {@link bbp_get_reply_approve_link()}
+ */
+function bbp_reply_approve_link( $args = array() ) {
+	echo bbp_get_reply_approve_link( $args );
+}
+
+	/**
+	 * Return the approve link of the reply
+	 *
+	 * @since 2.6.0 bbPress (r5507)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Reply id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - sep: Separator between links
+	 *  - approve_text: Approve text
+	 *  - unapprove_text: Unapprove text
+	 * @return string Reply approve link
+	 */
+	function bbp_get_reply_approve_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'             => 0,
+			'link_before'    => '',
+			'link_after'     => '',
+			'sep'            => ' | ',
+			'approve_text'   => _x( 'Approve',   'Pending Status', 'bbpress' ),
+			'unapprove_text' => _x( 'Unapprove', 'Pending Status', 'bbpress' )
+		), 'get_reply_approve_link' );
+
+		// Get reply
+		$reply = bbp_get_reply( $r['id'] );
+
+		// Bail if no reply or current user cannot moderate
+		if ( empty( $reply ) || ! current_user_can( 'moderate', $reply->ID ) ) {
+			return;
+		}
+
+		$display = bbp_is_reply_pending( $reply->ID ) ? $r['approve_text'] : $r['unapprove_text'];
+		$uri     = add_query_arg( array( 'action' => 'bbp_toggle_reply_approve', 'reply_id' => $reply->ID ) );
+		$uri     = wp_nonce_url( $uri, 'approve-reply_' . $reply->ID );
+		$retval  = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-reply-approve-link">' . $display . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_approve_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the row class of a reply
+ *
+ * @since 2.0.0 bbPress (r2678)
+ *
+ * @param int $reply_id Optional. Reply ID
+ * @param array Extra classes you can pass when calling this function
+ */
+function bbp_reply_class( $reply_id = 0, $classes = array() ) {
+	echo bbp_get_reply_class( $reply_id, $classes );
+}
+	/**
+	 * Return the row class of a reply
+	 *
+	 * @since 2.0.0 bbPress (r2678)
+	 *
+	 * @param int $reply_id Optional. Reply ID
+	 * @param array Extra classes you can pass when calling this function
+	 * @return string Row class of the reply
+	 */
+	function bbp_get_reply_class( $reply_id = 0, $classes = array() ) {
+		$bbp       = bbpress();
+		$reply_id  = bbp_get_reply_id( $reply_id );
+		$topic_id  = bbp_get_reply_topic_id( $reply_id );
+		$forum_id  = bbp_get_reply_forum_id( $reply_id );
+		$author_id = bbp_get_reply_author_id( $reply_id );
+		$reply_pos = bbp_get_reply_position( $reply_id, true );
+		$classes   = array_filter( (array) $classes );
+		$count     = isset( $bbp->reply_query->current_post )
+			? (int) $bbp->reply_query->current_post
+			: 1;
+
+		//  Stripes
+		$even_odd = ( $count % 2 )
+			? 'even'
+			: 'odd';
+
+		// Forum moderator replied to topic
+		$forum_moderator = ( bbp_is_user_forum_moderator( $author_id, $forum_id ) === $author_id )
+			? 'forum-mod'
+			: '';
+
+		// Topic author replied to others
+		$topic_author = ( bbp_get_topic_author_id( $topic_id ) === $author_id )
+			? 'topic-author'
+			: '';
+
+		// Get reply classes
+		$reply_classes = array(
+			'loop-item-'          . $count,
+			'user-id-'            . $author_id,
+			'bbp-parent-forum-'   . $forum_id,
+			'bbp-parent-topic-'   . $topic_id,
+			'bbp-reply-position-' . $reply_pos,
+			$even_odd,
+			$topic_author,
+			$forum_moderator
+		);
+
+		// Run the topic classes through the post-class filters, which also
+		// handles the escaping of each individual class.
+		$post_classes = get_post_class( array_merge( $classes, $reply_classes ), $reply_id );
+
+		// Filter
+		$new_classes  = apply_filters( 'bbp_get_reply_class', $post_classes, $reply_id, $classes );
+
+		// Return
+		return 'class="' . implode( ' ', $new_classes ) . '"';
+	}
+
+/** Pagination ****************************************************************/
+
+/**
+ * Return the base URL used inside of pagination links
+ *
+ * @since 2.6.0 bbPress (r6679)
+ *
+ * @param int $topic_id
+ * @return string
+ */
+function bbp_get_replies_pagination_base( $topic_id = 0 ) {
+
+	// If pretty permalinks are enabled, make our pagination pretty
+	if ( bbp_use_pretty_urls() && ! bbp_is_topic_pending( $topic_id )) {
+
+		// User's replies
+		if ( bbp_is_single_user_replies() ) {
+			$base = bbp_get_user_replies_created_url( bbp_get_displayed_user_id() );
+
+		// Root profile page
+		} elseif ( bbp_is_single_user() ) {
+			$base = bbp_get_user_profile_url( bbp_get_displayed_user_id() );
+
+		// Page or single post
+		} elseif ( is_page() || is_single() ) {
+			$base = get_permalink();
+
+		// Single topic
+		} else {
+			$base = get_permalink( $topic_id );
+		}
+
+		$base = trailingslashit( $base ) . user_trailingslashit( bbp_get_paged_slug() . '/%#%/' );
+
+	// Unpretty permalinks
+	} else {
+		$base = add_query_arg( 'paged', '%#%' );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_replies_pagination_base', $base, $topic_id );
+}
+
+/**
+ * Output the topic pagination count
+ *
+ * The results are unescaped by design, to allow them to be filtered freely via
+ * the 'bbp_get_topic_pagination_count' filter.
+ *
+ * @since 2.0.0 bbPress (r2519)
+ *
+ */
+function bbp_topic_pagination_count() {
+	echo bbp_get_topic_pagination_count();
+}
+	/**
+	 * Return the topic pagination count
+	 *
+	 * @since 2.0.0 bbPress (r2519)
+	 *
+	 * @return string Topic pagination count
+	 */
+	function bbp_get_topic_pagination_count() {
+		$bbp = bbpress();
+
+		// Define local variable(s)
+		$retstr = '';
+
+		// Set pagination values
+		$count_int = intval( $bbp->reply_query->post_count     );
+		$total_int = intval( $bbp->reply_query->found_posts    );
+		$ppp_int   = intval( $bbp->reply_query->posts_per_page );
+		$start_int = intval( ( $bbp->reply_query->paged - 1 ) * $ppp_int ) + 1;
+		$to_int    = intval( ( $start_int + ( $ppp_int - 1 ) > $total_int )
+			? $total_int
+			: $start_int + ( $ppp_int - 1 ) );
+
+		// Format numbers for display
+		$count_num = bbp_number_format( $count_int );
+		$total_num = bbp_number_format( $total_int );
+		$from_num  = bbp_number_format( $start_int );
+		$to_num    = bbp_number_format( $to_int    );
+
+		// We are threading replies
+		if ( bbp_thread_replies() ) {
+			$walker  = new BBP_Walker_Reply();
+			$threads = absint( $walker->get_number_of_root_elements( $bbp->reply_query->posts ) - 1 );
+			$retstr  = sprintf( _n( 'Viewing %1$s reply thread', 'Viewing %1$s reply threads', $threads, 'bbpress' ), bbp_number_format( $threads ) );
+
+		// We are not including the lead topic
+		} elseif ( bbp_show_lead_topic() ) {
+
+			// Several replies in a topic with a single page
+			if ( empty( $to_num ) ) {
+				$retstr = sprintf( _n( 'Viewing %1$s reply', 'Viewing %1$s replies', $total_int, 'bbpress' ), $total_num );
+
+			// Several replies in a topic with several pages
+			} else {
+				$retstr = sprintf( _n( 'Viewing %2$s replies (of %4$s total)', 'Viewing %1$s replies - %2$s through %3$s (of %4$s total)', $count_int, 'bbpress' ), $count_num, $from_num, $to_num, $total_num );
+			}
+
+		// We are including the lead topic
+		} else {
+
+			// Several posts in a topic with a single page
+			if ( empty( $to_num ) ) {
+				$retstr = sprintf( _n( 'Viewing %1$s post', 'Viewing %1$s posts', $total_int, 'bbpress' ), $total_num );
+
+			// Several posts in a topic with several pages
+			} else {
+				$retstr = sprintf( _n( 'Viewing %2$s post (of %4$s total)', 'Viewing %1$s posts - %2$s through %3$s (of %4$s total)', $count_int, 'bbpress' ), $count_num, $from_num, $to_num, $total_num );
+			}
+		}
+
+		// Escape results of _n()
+		$retstr = esc_html( $retstr );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_pagination_count', $retstr );
+	}
+
+/**
+ * Output topic pagination links
+ *
+ * @since 2.0.0 bbPress (r2519)
+ */
+function bbp_topic_pagination_links() {
+	echo bbp_get_topic_pagination_links();
+}
+	/**
+	 * Return topic pagination links
+	 *
+	 * @since 2.0.0 bbPress (r2519)
+	 *
+	 * @return string Topic pagination links
+	 */
+	function bbp_get_topic_pagination_links() {
+		$bbp = bbpress();
+
+		if ( ! isset( $bbp->reply_query->pagination_links ) || empty( $bbp->reply_query->pagination_links ) ) {
+			return false;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_pagination_links', $bbp->reply_query->pagination_links );
+	}
+
+/** Forms *********************************************************************/
+
+/**
+ * Output the value of reply content field
+ *
+ * @since 2.0.0 bbPress (r3130)
+ */
+function bbp_form_reply_content() {
+	echo bbp_get_form_reply_content();
+}
+	/**
+	 * Return the value of reply content field
+	 *
+	 * @since 2.0.0 bbPress (r3130)
+	 *
+	 * @return string Value of reply content field
+	 */
+	function bbp_get_form_reply_content() {
+
+		// Get _POST data
+		if ( bbp_is_reply_form_post_request() && isset( $_POST['bbp_reply_content'] ) ) {
+			$reply_content = wp_unslash( $_POST['bbp_reply_content'] );
+
+		// Get edit data
+		} elseif ( bbp_is_reply_edit() ) {
+			$reply_content = bbp_get_global_post_field( 'post_content', 'raw' );
+
+		// No data
+		} else {
+			$reply_content = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_reply_content', $reply_content );
+	}
+
+/**
+ * Output the value of the reply to field
+ *
+ * @since 2.4.0 bbPress (r4944)
+ */
+function bbp_form_reply_to() {
+	echo bbp_get_form_reply_to();
+}
+
+	/**
+	 * Return the value of reply to field
+	 *
+	 * @since 2.4.0 bbPress (r4944)
+	 *
+	 * @return string Value of reply to field
+	 */
+	function bbp_get_form_reply_to() {
+
+		// Set initial value
+		$reply_to = 0;
+
+		// Get $_REQUEST data
+		if ( isset( $_REQUEST['bbp_reply_to'] ) ) {
+			$reply_to = bbp_validate_reply_to( $_REQUEST['bbp_reply_to'] );
+		}
+
+		// If empty, get from meta
+		if ( empty( $reply_to ) ) {
+			$reply_to = bbp_get_reply_to();
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_reply_to', $reply_to );
+	}
+
+/**
+ * Output a select box allowing to pick which reply an existing hierarchical
+ * reply belongs to.
+ *
+ * @since 2.6.0 bbPress (r5387)
+ *
+ * @param int $reply_id
+ */
+function bbp_reply_to_dropdown( $reply_id = 0 ) {
+	echo bbp_get_reply_to_dropdown( $reply_id );
+}
+	/**
+	 * Return a select box allowing to pick which topic/reply a reply belongs.
+	 *
+	 * @since 2.6.0 bbPress (r5387)
+	 *
+	 * @param int $reply_id
+	 *
+	 * @return string The dropdown
+	 */
+	function bbp_get_reply_to_dropdown( $reply_id = 0 ) {
+
+		// Validate the reply data
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$reply_to = bbp_get_reply_to( $reply_id );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Get the replies
+		$posts = get_posts( array(
+			'post_type'   => bbp_get_reply_post_type(),
+			'post_status' => bbp_get_public_status_id(),
+			'post_parent' => $topic_id,
+			'numberposts' => -1,
+			'orderby'     => 'menu_order',
+			'order'       => 'ASC',
+		) );
+
+		// Append `reply_to` for each reply so it can be walked
+		foreach ( $posts as &$post ) {
+
+			// Check for reply post type
+			$_reply_to = bbp_get_reply_to( $post->ID );
+
+			// Make sure it's a reply to a reply
+			if ( empty( $_reply_to ) || ( $topic_id === $_reply_to ) ) {
+				$_reply_to = 0;
+			}
+
+			// Add reply_to to the post object so we can walk it later
+			$post->reply_to = $_reply_to;
+		}
+
+		// Default "None" text
+		$show_none = ( 0 === $reply_id )
+			? esc_attr_x( 'None', 'Default reply to dropdown text', 'bbpress' )
+			: sprintf( esc_attr__( '%1$s - %2$s', 'bbpress' ), $topic_id, bbp_get_topic_title( $topic_id ) );
+
+		// Get the dropdown and return it
+		$retval = bbp_get_dropdown( array(
+			'show_none'    => $show_none,
+			'select_id'    => 'bbp_reply_to',
+			'select_class' => 'bbp_dropdown',
+			'exclude'      => $reply_id,
+			'selected'     => $reply_to,
+			'post_parent'  => $topic_id,
+			'post_type'    => bbp_get_reply_post_type(),
+			'max_depth'    => bbp_thread_replies_depth(),
+			'page'         => 1,
+			'per_page'     => -1,
+			'walker'       => new BBP_Walker_Reply_Dropdown(),
+			'posts'        => $posts
+		) );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_reply_to_dropdown', $retval, $reply_id, $reply_to, $topic_id );
+	}
+
+/**
+ * Output checked value of reply log edit field
+ *
+ * @since 2.0.0 bbPress (r3130)
+ */
+function bbp_form_reply_log_edit() {
+	echo bbp_get_form_reply_log_edit();
+}
+	/**
+	 * Return checked value of reply log edit field
+	 *
+	 * @since 2.0.0 bbPress (r3130)
+	 *
+	 * @return string Reply log edit checked value
+	 */
+	function bbp_get_form_reply_log_edit() {
+
+		// Get _POST data
+		if ( bbp_is_reply_form_post_request() && isset( $_POST['bbp_log_reply_edit'] ) ) {
+			$reply_revision = (bool) $_POST['bbp_log_reply_edit'];
+
+		// No data
+		} else {
+			$reply_revision = true;
+		}
+
+		// Get checked output
+		$checked = checked( $reply_revision, true, false );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_reply_log_edit', $checked, $reply_revision );
+	}
+
+/**
+ * Output the value of the reply edit reason
+ *
+ * @since 2.0.0 bbPress (r3130)
+ */
+function bbp_form_reply_edit_reason() {
+	echo bbp_get_form_reply_edit_reason();
+}
+	/**
+	 * Return the value of the reply edit reason
+	 *
+	 * @since 2.0.0 bbPress (r3130)
+	 *
+	 * @return string Reply edit reason value
+	 */
+	function bbp_get_form_reply_edit_reason() {
+
+		// Get _POST data
+		if ( bbp_is_reply_form_post_request() && isset( $_POST['bbp_reply_edit_reason'] ) ) {
+			$reply_edit_reason = wp_unslash( $_POST['bbp_reply_edit_reason'] );
+
+		// No data
+		} else {
+			$reply_edit_reason = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_reply_edit_reason', $reply_edit_reason );
+	}
+
+/**
+ * Output value reply status dropdown
+ *
+ * @since 2.6.0 bbPress (r5399)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_reply_status
+ *  - tab: Deprecated. Tabindex
+ *  - reply_id: Reply id
+ *  - selected: Override the selected option
+ */
+function bbp_form_reply_status_dropdown( $args = array() ) {
+	echo bbp_get_form_reply_status_dropdown( $args );
+}
+	/**
+	 * Returns reply status dropdown
+	 *
+	 * This dropdown is only intended to be seen by users with the 'moderate'
+	 * capability. Because of this, no additional capability checks are performed
+	 * within this function to check available reply statuses.
+	 *
+	 * @since 2.6.0 bbPress (r5399)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_reply_status
+	 *  - tab: Deprecated. Tabindex
+	 *  - reply_id: Reply id
+	 *  - selected: Override the selected option
+	 */
+	function bbp_get_form_reply_status_dropdown( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_reply_status',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'reply_id'     => 0,
+			'selected'     => false
+		), 'reply_status_dropdown' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_reply_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value was passed
+			} else {
+
+				// Edit reply
+				if ( bbp_is_reply_edit() ) {
+					$r['reply_id'] = bbp_get_reply_id( $r['reply_id'] );
+					$r['selected'] = bbp_get_reply_status( $r['reply_id'] );
+
+				// New reply
+				} else {
+					$r['selected'] = bbp_get_public_status_id();
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ) ?>" id="<?php echo esc_attr( $r['select_id'] ); ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_reply_statuses( $r['reply_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_reply_status_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/**
+ * Verify if a POST request came from a failed reply attempt.
+ *
+ * Used to avoid cross-site request forgeries when checking posted reply form
+ * content.
+ *
+ * @see bbp_reply_form_fields()
+ *
+ * @since 2.6.0 bbPress (r5558)
+ *
+ * @return boolean True if is a post request with valid nonce
+ */
+function bbp_is_reply_form_post_request() {
+
+	// Bail if not a post request
+	if ( ! bbp_is_post_request() ) {
+		return false;
+	}
+
+	// Creating a new reply
+	if ( bbp_verify_nonce_request( 'bbp-new-reply' ) ) {
+		return true;
+	}
+
+	// Editing an existing reply
+	if ( bbp_verify_nonce_request( 'bbp-edit-reply' ) ) {
+		return true;
+	}
+
+	return false;
+}
Index: /tags/2.6.6/src/includes/search/functions.php
===================================================================
--- /tags/2.6.6/src/includes/search/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/search/functions.php	(revision 7162)
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * bbPress Search Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Query *********************************************************************/
+
+/**
+ * Run the search query
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @param mixed $new_args New arguments
+ * @return bool False if no results, otherwise if search results are there
+ */
+function bbp_search_query( $new_args = array() ) {
+
+	// Existing arguments
+	$query_args = bbp_get_search_query_args();
+
+	// Merge arguments
+	if ( ! empty( $new_args ) ) {
+		$new_args   = bbp_parse_args( $new_args, array(), 'search_query' );
+		$query_args = array_merge( $query_args, $new_args );
+	}
+
+	return bbp_has_search_results( $query_args );
+}
+
+/**
+ * Return the search query args
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @return array Query arguments
+ */
+function bbp_get_search_query_args() {
+
+	// Get search terms
+	$search_terms = bbp_get_search_terms();
+	$retval       = ! empty( $search_terms )
+		? array( 's' => $search_terms )
+		: array();
+
+	// Filter & return
+	return apply_filters( 'bbp_get_search_query_args', $retval );
+}
+
+/**
+ * Redirect to search results page if needed
+ *
+ * @since 2.4.0 bbPress (r4928)
+ *
+ * @return If a redirect is not needed
+ */
+function bbp_search_results_redirect() {
+
+	// Bail if not a search request action
+	if ( empty( $_GET['action'] ) || ( 'bbp-search-request' !== $_GET['action'] ) ) {
+		return;
+	}
+
+	// Bail if not using pretty permalinks
+	if ( ! bbp_use_pretty_urls() ) {
+		return;
+	}
+
+	// Get the redirect URL
+	$redirect_to = bbp_get_search_results_url();
+	if ( empty( $redirect_to ) ) {
+		return;
+	}
+
+	// Redirect and bail
+	bbp_redirect( $redirect_to );
+}
+
+/**
+ * Return an array of search types
+ *
+ * @since 2.6.0 bbPress (r6903)
+ *
+ * @return array
+ */
+function bbp_get_search_type_ids() {
+	return apply_filters( 'bbp_get_search_types', array( 's', 'fs', 'ts', 'rs' ) );
+}
+
+/**
+ * Sanitize a query argument used to pass some search terms.
+ *
+ * Accepts a single parameter to be used for forums, topics, or replies.
+ *
+ * @since 2.6.0 bbPress (r6903)
+ *
+ * @param string $query_arg s|fs|ts|rs
+ *
+ * @return mixed
+ */
+function bbp_sanitize_search_request( $query_arg = 's' ) {
+
+	// Define allowed keys
+	$allowed = bbp_get_search_type_ids();
+
+	// Bail if not an allowed query string key
+	if ( ! in_array( $query_arg, $allowed, true ) ) {
+		return false;
+	}
+
+	// Get search terms if requested
+	$terms = ! empty( $_REQUEST[ $query_arg ] )
+		? $_REQUEST[ $query_arg ]
+		: false;
+
+	// Bail if query argument does not exist
+	if ( empty( $terms ) ) {
+		return false;
+	}
+
+	// Maybe implode if an array
+	if ( is_array( $terms ) ) {
+		$terms = implode( ' ', $terms );
+	}
+
+	// Sanitize
+	$retval = sanitize_title( trim( $terms ) );
+
+	// Filter & return
+	return apply_filters( 'bbp_sanitize_search_request', $retval, $query_arg );
+}
Index: /tags/2.6.6/src/includes/search/index.php
===================================================================
--- /tags/2.6.6/src/includes/search/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/search/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/search/template.php
===================================================================
--- /tags/2.6.6/src/includes/search/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/search/template.php	(revision 7162)
@@ -0,0 +1,450 @@
+<?php
+
+/**
+ * bbPress Search Template Tags
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Search Loop Functions *****************************************************/
+
+/**
+ * The main search loop. WordPress does the heavy lifting.
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @param array $args All the arguments supported by {@link WP_Query}
+ * @return object Multidimensional array of search information
+ */
+function bbp_has_search_results( $args = array() ) {
+
+	/** Defaults **************************************************************/
+
+	$default_search_terms = bbp_get_search_terms();
+	$default_post_types   = bbp_get_post_types();
+
+	// Default query args
+	$default = array(
+		'post_type'           => $default_post_types,        // Forums, topics, and replies
+		'posts_per_page'      => bbp_get_replies_per_page(), // This many
+		'paged'               => bbp_get_paged(),            // On this page
+		'orderby'             => 'date',                     // Sorted by date
+		'order'               => 'DESC',                     // Most recent first
+		'ignore_sticky_posts' => true,                       // Stickies not supported,
+
+		// Conditionally prime the cache for last active posts
+		'update_post_family_cache' => true
+	);
+
+	// Only set 's' if search terms exist
+	// https://bbpress.trac.wordpress.org/ticket/2607
+	if ( false !== $default_search_terms ) {
+		$default['s'] = $default_search_terms;
+	}
+
+	// What are the default allowed statuses (based on user caps)
+	if ( bbp_get_view_all() ) {
+
+		// Default view=all statuses
+		$post_statuses = array_keys( bbp_get_topic_statuses() );
+
+		// Add support for private status
+		if ( current_user_can( 'read_private_topics' ) ) {
+			$post_statuses[] = bbp_get_private_status_id();
+		}
+
+		// Join post statuses together
+		$default['post_status'] = $post_statuses;
+
+	// Lean on the 'perm' query var value of 'readable' to provide statuses
+	} else {
+		$default['perm'] = 'readable';
+	}
+
+	/** Setup *****************************************************************/
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, $default, 'has_search_results' );
+
+	// Get bbPress
+	$bbp = bbpress();
+
+	// Only call the search query if 's' is not empty
+	if ( ! empty( $r['s'] ) ) {
+		$bbp->search_query = new WP_Query( $r );
+	}
+
+	// Maybe prime last active posts
+	if ( ! empty( $r['update_post_family_cache'] ) ) {
+		bbp_update_post_family_caches( $bbp->search_query->posts );
+	}
+
+	// Add pagination values to query object
+	$bbp->search_query->posts_per_page = (int) $r['posts_per_page'];
+	$bbp->search_query->paged          = (int) $r['paged'];
+
+	// Never home, regardless of what parse_query says
+	$bbp->search_query->is_home        = false;
+
+	// Only add pagination is query returned results
+	if ( ! empty( $bbp->search_query->found_posts ) && ! empty( $bbp->search_query->posts_per_page ) ) {
+
+		// Total for pagination boundaries
+		$total_pages = ( $bbp->search_query->posts_per_page === $bbp->search_query->found_posts )
+			? 1
+			: ceil( $bbp->search_query->found_posts / $bbp->search_query->posts_per_page );
+
+		// Pagination settings with filter
+		$bbp_search_pagination = apply_filters( 'bbp_search_results_pagination', array(
+			'base'    => bbp_get_search_pagination_base(),
+			'total'   => $total_pages,
+			'current' => $bbp->search_query->paged
+		) );
+
+		// Add pagination to query object
+		$bbp->search_query->pagination_links = bbp_paginate_links( $bbp_search_pagination );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_has_search_results', $bbp->search_query->have_posts(), $bbp->search_query );
+}
+
+/**
+ * Whether there are more search results available in the loop
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @return object Search information
+ */
+function bbp_search_results() {
+
+	// Put into variable to check against next
+	$have_posts = bbpress()->search_query->have_posts();
+
+	// Reset the post data when finished
+	if ( empty( $have_posts ) ) {
+		wp_reset_postdata();
+	}
+
+	return $have_posts;
+}
+
+/**
+ * Loads up the current search result in the loop
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @return object Search information
+ */
+function bbp_the_search_result() {
+	$search_result = bbpress()->search_query->the_post();
+
+	// Reset each current (forum|topic|reply) id
+	bbpress()->current_forum_id = bbp_get_forum_id();
+	bbpress()->current_topic_id = bbp_get_topic_id();
+	bbpress()->current_reply_id = bbp_get_reply_id();
+
+	return $search_result;
+}
+
+/**
+ * Output the search page title
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+function bbp_search_title() {
+	echo bbp_get_search_title();
+}
+
+	/**
+	 * Get the search page title
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 */
+	function bbp_get_search_title() {
+
+		// Get search terms
+		$search_terms = bbp_get_search_terms();
+
+		// No search terms specified
+		if ( empty( $search_terms ) ) {
+			$title = esc_html__( 'Search', 'bbpress' );
+
+		// Include search terms in title
+		} else {
+			$title = sprintf( esc_html__( "Search Results for '%s'", 'bbpress' ), esc_attr( $search_terms ) );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_title', $title, $search_terms );
+	}
+
+/**
+ * Output the search url
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+function bbp_search_url() {
+	echo esc_url( bbp_get_search_url() );
+}
+	/**
+	 * Return the search url
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @return string Search url
+	 */
+	function bbp_get_search_url() {
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Run through home_url()
+			$url = bbp_get_root_url() . bbp_get_search_slug();
+			$url = user_trailingslashit( $url );
+			$url = home_url( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_search_rewrite_id() => ''
+			), home_url( '/' ) );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_url', $url );
+	}
+
+/**
+ * Output the search results url
+ *
+ * @since 2.4.0 bbPress (r4928)
+ */
+function bbp_search_results_url() {
+	echo esc_url( bbp_get_search_results_url() );
+}
+	/**
+	 * Return the search url
+	 *
+	 * @since 2.4.0 bbPress (r4928)
+	 *
+	 * @return string Search url
+	 */
+	function bbp_get_search_results_url() {
+
+		// Get the search terms
+		$search_terms = bbp_get_search_terms();
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Root search URL
+			$url = bbp_get_root_url() . bbp_get_search_slug();
+
+			// Append search terms
+			if ( ! empty( $search_terms ) ) {
+				$url = trailingslashit( $url ) . urlencode( $search_terms );
+			}
+
+			// Run through home_url()
+			$url = user_trailingslashit( $url );
+			$url = home_url( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_search_rewrite_id() => urlencode( $search_terms )
+			), home_url( '/' ) );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_results_url', $url );
+	}
+
+/**
+ * Output the search terms
+ *
+ * @since 2.3.0 bbPress (r4579)
+ *
+ * @param string $search_terms Optional. Search terms
+ */
+function bbp_search_terms( $search_terms = '' ) {
+	echo esc_attr( bbp_get_search_terms( $search_terms ) );
+}
+
+	/**
+	 * Get the search terms
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * If search terms are supplied, those are used. Otherwise check the
+	 * search rewrite id query var.
+	 *
+	 * @param string $passed_terms Optional. Search terms
+	 * @return bool|string Search terms on success, false on failure
+	 */
+	function bbp_get_search_terms( $passed_terms = '' ) {
+
+		// Sanitize terms if they were passed in
+		if ( ! empty( $passed_terms ) ) {
+			$search_terms = sanitize_title( $passed_terms );
+
+		// Use query variable if not
+		} else {
+
+			// Global
+			$search_terms = get_query_var( bbp_get_search_rewrite_id(), null );
+
+			// Searching globally
+			if ( ! is_null( $search_terms )  ) {
+				$search_terms = wp_unslash( $search_terms );
+
+			// Other searches
+			} else {
+
+				// Get known search type IDs
+				$types = bbp_get_search_type_ids();
+
+				// Filterable, so make sure types exist
+				if ( ! empty( $types ) ) {
+
+					// Loop through types
+					foreach ( $types as $type ) {
+
+						// Look for search terms
+						$terms = bbp_sanitize_search_request( $type );
+
+						// Skip if no terms
+						if ( empty( $terms ) ) {
+							continue;
+						}
+
+						// Set terms if not empty
+						$search_terms = $terms;
+					}
+				}
+			}
+		}
+
+		// Trim whitespace & decode if non-empty string, or set to false
+		$search_terms = ! empty( $search_terms ) && is_string( $search_terms )
+			? urldecode( trim( $search_terms ) )
+			: false;
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_terms', $search_terms, $passed_terms );
+	}
+
+/** Pagination ****************************************************************/
+
+/**
+ * Return the base URL used inside of pagination links
+ *
+ * @since 2.6.0 bbPress (r6679)
+ *
+ * @return string
+ */
+function bbp_get_search_pagination_base() {
+
+	// If pretty permalinks are enabled, make our pagination pretty
+	if ( bbp_use_pretty_urls() ) {
+
+		// Shortcode territory
+		if ( is_page() || is_single() ) {
+			$base = get_permalink();
+
+		// Default search location
+		} else {
+			$base = bbp_get_search_results_url();
+		}
+
+		// Add pagination base
+		$base = trailingslashit( $base ) . user_trailingslashit( bbp_get_paged_slug() . '/%#%/' );
+
+	// Unpretty permalinks
+	} else {
+		$base = add_query_arg( 'paged', '%#%' );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_search_pagination_base', $base );
+}
+
+/**
+ * Output the search result pagination count
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+function bbp_search_pagination_count() {
+	echo bbp_get_search_pagination_count();
+}
+
+	/**
+	 * Return the search results pagination count
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @return string Search pagination count
+	 */
+	function bbp_get_search_pagination_count() {
+		$bbp = bbpress();
+
+		// Define local variable(s)
+		$retstr = '';
+
+		// Set pagination values
+		$total_int = intval( $bbp->search_query->found_posts    );
+		$ppp_int   = intval( $bbp->search_query->posts_per_page );
+		$start_int = intval( ( $bbp->search_query->paged - 1 ) * $ppp_int ) + 1;
+		$to_int    = intval( ( $start_int + ( $ppp_int - 1 ) > $total_int )
+				? $total_int
+				: $start_int + ( $ppp_int - 1 ) );
+
+		// Format numbers for display
+		$total_num = bbp_number_format( $total_int );
+		$from_num  = bbp_number_format( $start_int );
+		$to_num    = bbp_number_format( $to_int    );
+
+		// Single page of results
+		if ( empty( $to_num ) ) {
+			$retstr = sprintf( _n( 'Viewing %1$s result', 'Viewing %1$s results', $total_int, 'bbpress' ), $total_num );
+
+		// Several pages of results
+		} else {
+			$retstr = sprintf( _n( 'Viewing %2$s results (of %4$s total)', 'Viewing %1$s results - %2$s through %3$s (of %4$s total)', $bbp->search_query->post_count, 'bbpress' ), $bbp->search_query->post_count, $from_num, $to_num, $total_num );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_pagination_count', esc_html( $retstr ) );
+	}
+
+/**
+ * Output search pagination links
+ *
+ * @since 2.3.0 bbPress (r4579)
+ */
+function bbp_search_pagination_links() {
+	echo bbp_get_search_pagination_links();
+}
+
+	/**
+	 * Return search pagination links
+	 *
+	 * @since 2.3.0 bbPress (r4579)
+	 *
+	 * @return string Search pagination links
+	 */
+	function bbp_get_search_pagination_links() {
+		$bbp = bbpress();
+
+		if ( ! isset( $bbp->search_query->pagination_links ) || empty( $bbp->search_query->pagination_links ) ) {
+			return false;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_search_pagination_links', $bbp->search_query->pagination_links );
+	}
Index: /tags/2.6.6/src/includes/topics/capabilities.php
===================================================================
--- /tags/2.6.6/src/includes/topics/capabilities.php	(revision 7162)
+++ /tags/2.6.6/src/includes/topics/capabilities.php	(revision 7162)
@@ -0,0 +1,389 @@
+<?php
+
+/**
+ * bbPress Topic Capabilites
+ *
+ * Used to map topic capabilities to WordPress's existing capabilities.
+ *
+ * @package bbPress
+ * @subpackage Capabilities
+ */
+
+/**
+ * Return topic capabilities
+ *
+ * @since 2.0.0 bbPress (r2593)
+ *
+ * @return array Topic capabilities
+ */
+function bbp_get_topic_caps() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_caps', array(
+		'edit_posts'          => 'edit_topics',
+		'edit_others_posts'   => 'edit_others_topics',
+		'publish_posts'       => 'publish_topics',
+		'read_private_posts'  => 'read_private_topics',
+		'read_hidden_posts'   => 'read_hidden_topics',
+		'delete_posts'        => 'delete_topics',
+		'delete_others_posts' => 'delete_others_topics'
+	) );
+}
+
+/**
+ * Return topic tag capabilities
+ *
+ * @since 2.0.0 bbPress (r2593)
+ *
+ *
+ * @return array Topic tag capabilities
+ */
+function bbp_get_topic_tag_caps() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_tag_caps', array(
+		'manage_terms' => 'manage_topic_tags',
+		'edit_terms'   => 'edit_topic_tags',
+		'delete_terms' => 'delete_topic_tags',
+		'assign_terms' => 'assign_topic_tags'
+	) );
+}
+
+/**
+ * Maps topic capabilities
+ *
+ * @since 2.2.0 bbPress (r4242)
+ *
+ * @param array  $caps    Capabilities for meta capability.
+ * @param string $cap     Capability name.
+ * @param int    $user_id User id.
+ * @param array  $args    Arguments.
+ *
+ * @return array Actual capabilities for meta capability
+ */
+function bbp_map_topic_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+
+		/** Reading ***********************************************************/
+
+		case 'read_topic' :
+
+			// User cannot spectate
+			if ( ! user_can( $user_id, 'spectate' ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Do some post ID based logic
+			} else {
+
+				// Bail if no post ID
+				if ( empty( $args[0] ) ) {
+					break;
+				}
+
+				// Get the post.
+				$_post = get_post( $args[0] );
+				if ( ! empty( $_post ) ) {
+
+					// Get caps for post type object
+					$post_type = get_post_type_object( $_post->post_type );
+
+					// Post is public
+					if ( bbp_get_public_status_id() === $_post->post_status ) {
+						$caps = array( 'spectate' );
+
+					// User is author so allow read
+					} elseif ( (int) $user_id === (int) $_post->post_author ) {
+						$caps = array( 'spectate' );
+
+					// Moderators can always edit forum content
+					} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+						$caps = array( 'spectate' );
+
+					// Unknown so map to private posts
+					} else {
+						$caps = array( $post_type->cap->read_private_posts );
+					}
+				}
+			}
+
+			break;
+
+		/** Publishing ********************************************************/
+
+		case 'publish_topics'  :
+
+			// Moderators can always publish
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		/** Editing ***********************************************************/
+
+		// Used primarily in wp-admin
+		case 'edit_topics'        :
+		case 'edit_others_topics' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( $cap );
+
+			// Otherwise, check forum
+			} else {
+				$forum_id = bbp_get_forum_id();
+
+				// Moderators can always edit forum content
+				if ( user_can( $user_id, 'moderate', $forum_id ) ) {
+					$caps = array( 'spectate' );
+
+				// Fallback to do_not_allow
+				} else {
+					$caps = array( 'do_not_allow' );
+				}
+			}
+
+			break;
+
+		// Used everywhere
+		case 'edit_topic' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post.
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always edit forum content
+				} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+					$caps = array( 'spectate' );
+
+				// User is author so allow edit if not in admin, unless it's past edit lock time
+				} elseif ( ! is_admin() && ( (int) $user_id === (int) $_post->post_author ) ) {
+
+					// If editing...
+					if ( bbp_is_topic_edit() ) {
+
+						// Only allow if not past the edit-lock period
+						$caps = ! bbp_past_edit_lock( $_post->post_date_gmt )
+							? array( $post_type->cap->edit_posts )
+							: array( 'do_not_allow' );
+
+					// Otherwise...
+					} else {
+						$caps = array( $post_type->cap->edit_posts );
+					}
+
+				// Unknown, so map to edit_others_posts
+				} else {
+					$caps = array( $post_type->cap->edit_others_posts );
+				}
+			}
+
+			break;
+
+		/** Deleting **********************************************************/
+
+		case 'delete_topic' :
+
+			// Bail if no post ID
+			if ( empty( $args[0] ) ) {
+				break;
+			}
+
+			// Get the post.
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always edit forum content
+				} elseif ( user_can( $user_id, 'moderate', $_post->ID ) ) {
+					$caps = array( 'spectate' );
+
+				// User is author so allow delete if not in admin
+				} elseif ( ! is_admin() && ( (int) $user_id === (int) $_post->post_author ) ) {
+					$caps = array( $post_type->cap->delete_posts );
+
+				// Unknown so map to delete_others_posts
+				} else {
+					$caps = array( $post_type->cap->delete_others_posts );
+				}
+			}
+
+			break;
+
+		// Moderation override
+		case 'delete_topics'         :
+		case 'delete_others_topics'  :
+
+			// Moderators can always delete
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( $cap );
+			}
+
+			break;
+
+		/** Admin *************************************************************/
+
+		case 'bbp_topics_admin' :
+			$caps = array( 'edit_topics' );
+			break;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_topic_meta_caps', $caps, $cap, $user_id, $args );
+}
+
+/**
+ * Maps topic tag capabilities
+ *
+ * @since 2.2.0 bbPress (r4242)
+ *
+ * @param array $caps Capabilities for meta capability
+ * @param string $cap Capability name
+ * @param int $user_id User id
+ * @param array $args Arguments
+ *
+ * @return array Actual capabilities for meta capability
+ */
+function bbp_map_topic_tag_meta_caps( $caps, $cap, $user_id, $args ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+
+		/** Assignment ********************************************************/
+
+		case 'assign_topic_tags' :
+
+			// Get post
+			$post_id = ! empty( $args[0] )
+				? get_post( $args[0] )->ID
+				: 0;
+
+			// Add 'do_not_allow' cap if user is spam or deleted
+			if ( bbp_is_user_inactive( $user_id ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Moderators can always assign
+			} elseif ( user_can( $user_id, 'moderate', $post_id ) ) {
+				$caps = array( 'moderate' );
+
+			// Do not allow if topic tags are disabled
+			} elseif ( ! bbp_allow_topic_tags() ) {
+				$caps = array( 'do_not_allow' );
+			}
+
+			break;
+
+		/** Management ********************************************************/
+
+		case 'manage_topic_tags' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		/** Editing ***********************************************************/
+
+		case 'edit_topic_tags' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		case 'edit_topic_tag' :
+
+			// Get the term
+			$_tag = get_term( $args[0], bbp_get_topic_tag_tax_id() );
+			if ( ! empty( $_tag ) ) {
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always edit topic tags
+				} elseif ( user_can( $user_id, 'moderate', $_tag->term_id ) ) {
+					$caps = array( 'spectate' );
+
+				// Fallback to edit_terms.
+				} else {
+					$taxonomy = get_taxonomy( bbp_get_topic_tag_tax_id() );
+					$caps     = array( $taxonomy->cap->edit_terms );
+				}
+			}
+
+			break;
+
+		/** Deleting **********************************************************/
+
+		case 'delete_topic_tags' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+
+		case 'delete_topic_tag' :
+
+			// Get the term
+			$_tag = get_term( $args[0], bbp_get_topic_tag_tax_id() );
+			if ( ! empty( $_tag ) ) {
+
+				// Add 'do_not_allow' cap if user is spam or deleted
+				if ( bbp_is_user_inactive( $user_id ) ) {
+					$caps = array( 'do_not_allow' );
+
+				// Moderators can always delete topic tags
+				} elseif ( user_can( $user_id, 'moderate', $_tag->term_id ) ) {
+					$caps = array( 'spectate' );
+
+				// Fallback to delete_terms.
+				} else {
+					$taxonomy = get_taxonomy( $_tag->post_type );
+					$caps     = array( $taxonomy->cap->delete_terms );
+				}
+			}
+
+			break;
+
+		/** Admin *************************************************************/
+
+		case 'bbp_topic_tags_admin' :
+
+			// Moderators can always edit
+			if ( user_can( $user_id, 'moderate' ) ) {
+				$caps = array( 'moderate' );
+			}
+
+			break;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_topic_tag_meta_caps', $caps, $cap, $user_id, $args );
+}
Index: /tags/2.6.6/src/includes/topics/functions.php
===================================================================
--- /tags/2.6.6/src/includes/topics/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/topics/functions.php	(revision 7162)
@@ -0,0 +1,3861 @@
+<?php
+
+/**
+ * bbPress Topic Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Insert ********************************************************************/
+
+/**
+ * A wrapper for wp_insert_post() that also includes the necessary meta values
+ * for the topic to function properly.
+ *
+ * @since 2.0.0 bbPress (r3349)
+ *
+ * @param array $topic_data Forum post data
+ * @param arrap $topic_meta Forum meta data
+ */
+function bbp_insert_topic( $topic_data = array(), $topic_meta = array() ) {
+
+	// Parse arguments against default values
+	$topic_data = bbp_parse_args( $topic_data, array(
+		'post_parent'    => 0, // forum ID
+		'post_status'    => bbp_get_public_status_id(),
+		'post_type'      => bbp_get_topic_post_type(),
+		'post_author'    => bbp_get_current_user_id(),
+		'post_password'  => '',
+		'post_content'   => '',
+		'post_title'     => '',
+		'comment_status' => 'closed',
+		'menu_order'     => 0
+	), 'insert_topic' );
+
+	// Insert topic
+	$topic_id = wp_insert_post( $topic_data, false );
+
+	// Bail if no topic was added
+	if ( empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Parse arguments against default values
+	$topic_meta = bbp_parse_args( $topic_meta, array(
+		'author_ip'          => bbp_current_author_ip(),
+		'forum_id'           => 0,
+		'topic_id'           => $topic_id,
+		'voice_count'        => 1,
+		'reply_count'        => 0,
+		'reply_count_hidden' => 0,
+		'last_reply_id'      => 0,
+		'last_active_id'     => $topic_id,
+		'last_active_time'   => get_post_field( 'post_date', $topic_id, 'db' )
+	), 'insert_topic_meta' );
+
+	// Insert topic meta
+	foreach ( $topic_meta as $meta_key => $meta_value ) {
+
+		// Prefix if not prefixed
+		if ( '_bbp_' !== substr( $meta_key, 0, 5 ) ) {
+			$meta_key = '_bbp_' . $meta_key;
+		}
+
+		// Update the meta
+		update_post_meta( $topic_id, $meta_key, $meta_value );
+	}
+
+	// Update the topic and hierarchy
+	bbp_update_topic( $topic_id, $topic_meta['forum_id'], array(), $topic_data['post_author'], false );
+
+	/**
+	 * Fires after topic has been inserted via `bbp_insert_topic`.
+	 *
+	 * @since 2.6.0 bbPress (r6036)
+	 *
+	 * @param int $topic_id               The topic id.
+	 * @param int $topic_meta['forum_id'] The topic forum meta.
+	 */
+	do_action( 'bbp_insert_topic', (int) $topic_id, (int) $topic_meta['forum_id'] );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/** Post Form Handlers ********************************************************/
+
+/**
+ * Handles the front end topic submission
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_new_topic_handler( $action = '' ) {
+
+	// Bail if action is not bbp-new-topic
+	if ( 'bbp-new-topic' !== $action ) {
+		return;
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-new-topic' ) ) {
+		bbp_add_error( 'bbp_new_topic_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Define local variable(s)
+	$view_all = false;
+	$forum_id = $topic_author = 0;
+	$topic_title = $topic_content = '';
+	$anonymous_data = array();
+	$terms = array( bbp_get_topic_tag_tax_id() => array() );
+
+	/** Topic Author **********************************************************/
+
+	// User is anonymous
+	if ( bbp_is_anonymous() ) {
+
+		// Filter anonymous data (variable is used later)
+		$anonymous_data = bbp_filter_anonymous_post_data();
+
+		// Anonymous data checks out, so set cookies, etc...
+		bbp_set_current_anonymous_user_data( $anonymous_data );
+
+	// User is logged in
+	} else {
+
+		// User cannot create topics
+		if ( ! current_user_can( 'publish_topics' ) ) {
+			bbp_add_error( 'bbp_topic_permission', __( '<strong>Error</strong>: You do not have permission to create new topics.', 'bbpress' ) );
+			return;
+		}
+
+		// Topic author is current user
+		$topic_author = bbp_get_current_user_id();
+	}
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_topic'] ) && wp_create_nonce( 'bbp-unfiltered-html-topic_new' ) === $_POST['_bbp_unfiltered_html_topic'] ) {
+		remove_filter( 'bbp_new_topic_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_new_topic_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_new_topic_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Topic Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_topic_title'] ) ) {
+		$topic_title = sanitize_text_field( $_POST['bbp_topic_title'] );
+	}
+
+	// Filter and sanitize
+	$topic_title = apply_filters( 'bbp_new_topic_pre_title', $topic_title );
+
+	// No topic title
+	if ( empty( $topic_title ) ) {
+		bbp_add_error( 'bbp_topic_title', __( '<strong>Error</strong>: Your topic needs a title.', 'bbpress' ) );
+	}
+
+	// Title too long
+	if ( bbp_is_title_too_long( $topic_title ) ) {
+		bbp_add_error( 'bbp_topic_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Topic Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_topic_content'] ) ) {
+		$topic_content = $_POST['bbp_topic_content'];
+	}
+
+	// Filter and sanitize
+	$topic_content = apply_filters( 'bbp_new_topic_pre_content', $topic_content );
+
+	// No topic content
+	if ( empty( $topic_content ) ) {
+		bbp_add_error( 'bbp_topic_content', __( '<strong>Error</strong>: Your topic cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Topic Forum ***********************************************************/
+
+	// Error check the POST'ed topic id
+	if ( isset( $_POST['bbp_forum_id'] ) ) {
+
+		// Empty Forum id was passed
+		if ( empty( $_POST['bbp_forum_id'] ) ) {
+			bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
+
+		// Forum id is not a number
+		} elseif ( ! is_numeric( $_POST['bbp_forum_id'] ) ) {
+			bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID must be a number.', 'bbpress' ) );
+
+		// Forum id might be valid
+		} else {
+
+			// Get the forum id
+			$posted_forum_id = intval( $_POST['bbp_forum_id'] );
+
+			// Forum id is empty
+			if ( 0 === $posted_forum_id ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
+
+			// Forum id is a negative number
+			} elseif ( 0 > $posted_forum_id ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID cannot be a negative number.', 'bbpress' ) );
+
+			// Forum does not exist
+			} elseif ( ! bbp_get_forum( $posted_forum_id ) ) {
+				bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum does not exist.', 'bbpress' ) );
+
+			// Use the POST'ed forum id
+			} else {
+				$forum_id = $posted_forum_id;
+			}
+		}
+	}
+
+	// Forum exists
+	if ( ! empty( $forum_id ) ) {
+
+		// Forum is a category
+		if ( bbp_is_forum_category( $forum_id ) ) {
+			bbp_add_error( 'bbp_new_topic_forum_category', __( '<strong>Error</strong>: This forum is a category. No topics can be created in this forum.', 'bbpress' ) );
+
+		// Forum is not a category
+		} else {
+
+			// Forum is closed and user cannot access
+			if ( bbp_is_forum_closed( $forum_id ) && ! current_user_can( 'edit_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_topic_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new topics.', 'bbpress' ) );
+			}
+
+			// Forum is private and user cannot access
+			if ( bbp_is_forum_private( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_topic_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new topics in it.', 'bbpress' ) );
+
+			// Forum is hidden and user cannot access
+			} elseif ( bbp_is_forum_hidden( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_new_topic_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new topics in it.', 'bbpress' ) );
+			}
+		}
+	}
+
+	/** Topic Flooding ********************************************************/
+
+	if ( ! bbp_check_for_flood( $anonymous_data, $topic_author ) ) {
+		bbp_add_error( 'bbp_topic_flood', __( '<strong>Error</strong>: Slow down; you move too fast.', 'bbpress' ) );
+	}
+
+	/** Topic Duplicate *******************************************************/
+
+	if ( ! bbp_check_for_duplicate( array( 'post_type' => bbp_get_topic_post_type(), 'post_author' => $topic_author, 'post_content' => $topic_content, 'anonymous_data' => $anonymous_data ) ) ) {
+		bbp_add_error( 'bbp_topic_duplicate', __( '<strong>Error</strong>: Duplicate topic detected; it looks as though you&#8217;ve already said that.', 'bbpress' ) );
+	}
+
+	/** Topic Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, $topic_author, $topic_title, $topic_content, true ) ) {
+		bbp_add_error( 'bbp_topic_moderation', __( '<strong>Error</strong>: Your topic cannot be created at this time.', 'bbpress' ) );
+	}
+
+	/** Topic Status **********************************************************/
+
+	// Get available topic statuses
+	$topic_statuses = bbp_get_topic_statuses();
+
+	// Maybe put into moderation
+	if ( ! bbp_check_for_moderation( $anonymous_data, $topic_author, $topic_title, $topic_content ) ) {
+		$topic_status = bbp_get_pending_status_id();
+
+	// Check possible topic status ID's
+	} elseif ( ! empty( $_POST['bbp_topic_status'] ) && in_array( $_POST['bbp_topic_status'], array_keys( $topic_statuses ), true ) ) {
+		$topic_status = sanitize_key( $_POST['bbp_topic_status'] );
+
+	// Default to published if nothing else
+	} else {
+		$topic_status = bbp_get_public_status_id();
+	}
+
+	/** Topic Tags ************************************************************/
+
+	if ( bbp_allow_topic_tags() && ! empty( $_POST['bbp_topic_tags'] ) ) {
+
+		// Escape tag input
+		$terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
+
+		// Explode by comma
+		if ( strstr( $terms, ',' ) ) {
+			$terms = explode( ',', $terms );
+		}
+
+		// Add topic tag ID as main key
+		$terms = array( bbp_get_topic_tag_tax_id() => $terms );
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_new_topic_pre_extras', $forum_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $topic_data as an array.
+	// Just in time manipulation of topic data before being created
+	$topic_data = apply_filters( 'bbp_new_topic_pre_insert', array(
+		'post_author'    => $topic_author,
+		'post_title'     => $topic_title,
+		'post_content'   => $topic_content,
+		'post_status'    => $topic_status,
+		'post_parent'    => $forum_id,
+		'post_type'      => bbp_get_topic_post_type(),
+		'tax_input'      => $terms,
+		'comment_status' => 'closed'
+	) );
+
+	// Insert topic
+	$topic_id = wp_insert_post( $topic_data, true );
+
+	/** No Errors *************************************************************/
+
+	if ( ! empty( $topic_id ) && ! is_wp_error( $topic_id ) ) {
+
+		/** Close Check *******************************************************/
+
+		// If the topic is closed, close it properly
+		if ( ( get_post_field( 'post_status', $topic_id ) === bbp_get_closed_status_id() ) || ( $topic_data['post_status'] === bbp_get_closed_status_id() ) ) {
+
+			// Close the topic
+			bbp_close_topic( $topic_id );
+		}
+
+		/** Trash Check *******************************************************/
+
+		// If the forum is trash, or the topic_status is switched to
+		// trash, trash the topic properly
+		if ( ( get_post_field( 'post_status', $forum_id ) === bbp_get_trash_status_id() ) || ( $topic_data['post_status'] === bbp_get_trash_status_id() ) ) {
+
+			// Trash the topic
+			wp_trash_post( $topic_id );
+
+			// Force view=all
+			$view_all = true;
+		}
+
+		/** Spam Check ********************************************************/
+
+		// If the topic is spam, officially spam this topic
+		if ( $topic_data['post_status'] === bbp_get_spam_status_id() ) {
+			add_post_meta( $topic_id, '_bbp_spam_meta_status', bbp_get_public_status_id() );
+
+			// Force view=all
+			$view_all = true;
+		}
+
+		/** Update counts, etc... *********************************************/
+
+		do_action( 'bbp_new_topic', $topic_id, $forum_id, $anonymous_data, $topic_author );
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_new_topic_post_extras', $topic_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to = bbp_get_redirect_to();
+
+		// Get the topic URL
+		$redirect_url = bbp_get_topic_permalink( $topic_id, $redirect_to );
+
+		// Add view all?
+		if ( bbp_get_view_all() || ! empty( $view_all ) ) {
+
+			// User can moderate, so redirect to topic with view all set
+			if ( current_user_can( 'moderate', $topic_id ) ) {
+				$redirect_url = bbp_add_view_all( $redirect_url );
+
+			// User cannot moderate, so redirect to forum
+			} else {
+				$redirect_url = bbp_get_forum_permalink( $forum_id );
+			}
+		}
+
+		// Allow to be filtered
+		$redirect_url = apply_filters( 'bbp_new_topic_redirect_to', $redirect_url, $redirect_to, $topic_id );
+
+		/** Successful Save ***************************************************/
+
+		// Redirect back to new topic
+		bbp_redirect( $redirect_url );
+
+	/** Errors ****************************************************************/
+
+	// WP_Error
+	} elseif ( is_wp_error( $topic_id ) ) {
+		bbp_add_error( 'bbp_topic_error', sprintf( __( '<strong>Error</strong>: The following problem(s) occurred: %s', 'bbpress' ), $topic_id->get_error_message() ) );
+
+	// Generic error
+	} else {
+		bbp_add_error( 'bbp_topic_error', __( '<strong>Error</strong>: The topic was not created.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handles the front end edit topic submission
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_edit_topic_handler( $action = '' ) {
+
+	// Bail if action is not bbp-edit-topic
+	if ( 'bbp-edit-topic' !== $action ) {
+		return;
+	}
+
+	// Define local variable(s)
+	$revisions_removed = false;
+	$topic = $topic_id = $topic_author = $forum_id = 0;
+	$topic_title = $topic_content = $topic_edit_reason = '';
+	$anonymous_data = array();
+
+	/** Topic *****************************************************************/
+
+	// Topic id was not passed
+	if ( empty( $_POST['bbp_topic_id'] ) ) {
+		bbp_add_error( 'bbp_edit_topic_id', __( '<strong>Error</strong>: Topic ID not found.', 'bbpress' ) );
+		return;
+
+	// Topic id was passed
+	} elseif ( is_numeric( $_POST['bbp_topic_id'] ) ) {
+		$topic_id = (int) $_POST['bbp_topic_id'];
+		$topic    = bbp_get_topic( $topic_id );
+	}
+
+	// Topic does not exist
+	if ( empty( $topic ) ) {
+		bbp_add_error( 'bbp_edit_topic_not_found', __( '<strong>Error</strong>: The topic you want to edit was not found.', 'bbpress' ) );
+		return;
+
+	// Topic exists
+	} else {
+
+		// Check users ability to create new topic
+		if ( ! bbp_is_topic_anonymous( $topic_id ) ) {
+
+			// User cannot edit this topic
+			if ( ! current_user_can( 'edit_topic', $topic_id ) ) {
+				bbp_add_error( 'bbp_edit_topic_permission', __( '<strong>Error</strong>: You do not have permission to edit that topic.', 'bbpress' ) );
+			}
+
+			// Set topic author
+			$topic_author = bbp_get_topic_author_id( $topic_id );
+
+		// It is an anonymous post
+		} else {
+
+			// Filter anonymous data
+			$anonymous_data = bbp_filter_anonymous_post_data();
+		}
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-edit-topic_' . $topic_id ) ) {
+		bbp_add_error( 'bbp_edit_topic_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Remove kses filters from title and content for capable users and if the nonce is verified
+	if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_topic'] ) && ( wp_create_nonce( 'bbp-unfiltered-html-topic_' . $topic_id ) === $_POST['_bbp_unfiltered_html_topic'] ) ) {
+		remove_filter( 'bbp_edit_topic_pre_title',   'wp_filter_kses'      );
+		remove_filter( 'bbp_edit_topic_pre_content', 'bbp_encode_bad',  10 );
+		remove_filter( 'bbp_edit_topic_pre_content', 'bbp_filter_kses', 30 );
+	}
+
+	/** Topic Forum ***********************************************************/
+
+	// Forum id was not passed
+	if ( empty( $_POST['bbp_forum_id'] ) ) {
+		bbp_add_error( 'bbp_topic_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
+
+	// Forum id was passed
+	} elseif ( is_numeric( $_POST['bbp_forum_id'] ) ) {
+		$forum_id = (int) $_POST['bbp_forum_id'];
+	}
+
+	// Current forum this topic is in
+	$current_forum_id = bbp_get_topic_forum_id( $topic_id );
+
+	// Forum exists
+	if ( ! empty( $forum_id ) && ( $forum_id !== $current_forum_id ) ) {
+
+		// Forum is a category
+		if ( bbp_is_forum_category( $forum_id ) ) {
+			bbp_add_error( 'bbp_edit_topic_forum_category', __( '<strong>Error</strong>: This forum is a category. No topics can be created in it.', 'bbpress' ) );
+
+		// Forum is not a category
+		} else {
+
+			// Forum is closed and user cannot access
+			if ( bbp_is_forum_closed( $forum_id ) && ! current_user_can( 'edit_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_topic_forum_closed', __( '<strong>Error</strong>: This forum has been closed to new topics.', 'bbpress' ) );
+			}
+
+			// Forum is private and user cannot access
+			if ( bbp_is_forum_private( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_topic_forum_private', __( '<strong>Error</strong>: This forum is private and you do not have the capability to read or create new topics in it.', 'bbpress' ) );
+
+			// Forum is hidden and user cannot access
+			} elseif ( bbp_is_forum_hidden( $forum_id ) && ! current_user_can( 'read_forum', $forum_id ) ) {
+				bbp_add_error( 'bbp_edit_topic_forum_hidden', __( '<strong>Error</strong>: This forum is hidden and you do not have the capability to read or create new topics in it.', 'bbpress' ) );
+			}
+		}
+	}
+
+	/** Topic Title ***********************************************************/
+
+	if ( ! empty( $_POST['bbp_topic_title'] ) ) {
+		$topic_title = sanitize_text_field( $_POST['bbp_topic_title'] );
+	}
+
+	// Filter and sanitize
+	$topic_title = apply_filters( 'bbp_edit_topic_pre_title', $topic_title, $topic_id );
+
+	// No topic title
+	if ( empty( $topic_title ) ) {
+		bbp_add_error( 'bbp_edit_topic_title', __( '<strong>Error</strong>: Your topic needs a title.', 'bbpress' ) );
+	}
+
+	// Title too long
+	if ( bbp_is_title_too_long( $topic_title ) ) {
+		bbp_add_error( 'bbp_topic_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
+	}
+
+	/** Topic Content *********************************************************/
+
+	if ( ! empty( $_POST['bbp_topic_content'] ) ) {
+		$topic_content = $_POST['bbp_topic_content'];
+	}
+
+	// Filter and sanitize
+	$topic_content = apply_filters( 'bbp_edit_topic_pre_content', $topic_content, $topic_id );
+
+	// No topic content
+	if ( empty( $topic_content ) ) {
+		bbp_add_error( 'bbp_edit_topic_content', __( '<strong>Error</strong>: Your topic cannot be empty.', 'bbpress' ) );
+	}
+
+	/** Topic Bad Words *******************************************************/
+
+	if ( ! bbp_check_for_moderation( $anonymous_data, $topic_author, $topic_title, $topic_content, true ) ) {
+		bbp_add_error( 'bbp_topic_moderation', __( '<strong>Error</strong>: Your topic cannot be edited at this time.', 'bbpress' ) );
+	}
+
+	/** Topic Status **********************************************************/
+
+	// Get available topic statuses
+	$topic_statuses = bbp_get_topic_statuses( $topic_id );
+
+	// Maybe put into moderation
+	if ( ! bbp_check_for_moderation( $anonymous_data, $topic_author, $topic_title, $topic_content ) ) {
+
+		// Set post status to pending if public or closed
+		if ( bbp_is_topic_public( $topic->ID ) ) {
+			$topic_status = bbp_get_pending_status_id();
+		}
+
+	// Check possible topic status ID's
+	} elseif ( ! empty( $_POST['bbp_topic_status'] ) && in_array( $_POST['bbp_topic_status'], array_keys( $topic_statuses ), true ) ) {
+		$topic_status = sanitize_key( $_POST['bbp_topic_status'] );
+
+	// Use existing post_status
+	} else {
+		$topic_status = $topic->post_status;
+	}
+
+	/** Topic Tags ************************************************************/
+
+	// Either replace terms
+	if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', $topic_id ) && ! empty( $_POST['bbp_topic_tags'] ) ) {
+
+		// Escape tag input
+		$terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
+
+		// Explode by comma
+		if ( strstr( $terms, ',' ) ) {
+			$terms = explode( ',', $terms );
+		}
+
+		// Add topic tag ID as main key
+		$terms = array( bbp_get_topic_tag_tax_id() => $terms );
+
+	// ...or remove them.
+	} elseif ( isset( $_POST['bbp_topic_tags'] ) ) {
+		$terms = array( bbp_get_topic_tag_tax_id() => array() );
+
+	// Existing terms
+	} else {
+		$terms = array( bbp_get_topic_tag_tax_id() => explode( ',', bbp_get_topic_tag_names( $topic_id, ',' ) ) );
+	}
+
+	/** Additional Actions (Before Save) **************************************/
+
+	do_action( 'bbp_edit_topic_pre_extras', $topic_id );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Add the content of the form to $topic_data as an array
+	// Just in time manipulation of topic data before being edited
+	$topic_data = apply_filters( 'bbp_edit_topic_pre_insert', array(
+		'ID'           => $topic_id,
+		'post_title'   => $topic_title,
+		'post_content' => $topic_content,
+		'post_status'  => $topic_status,
+		'post_parent'  => $forum_id,
+		'post_author'  => $topic_author,
+		'post_type'    => bbp_get_topic_post_type(),
+		'tax_input'    => $terms,
+	) );
+
+	// Toggle revisions to avoid duplicates
+	if ( post_type_supports( bbp_get_topic_post_type(), 'revisions' ) ) {
+		$revisions_removed = true;
+		remove_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	// Insert topic
+	$topic_id = wp_update_post( $topic_data );
+
+	// Toggle revisions back on
+	if ( true === $revisions_removed ) {
+		$revisions_removed = false;
+		add_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	/** No Errors *************************************************************/
+
+	if ( ! empty( $topic_id ) && ! is_wp_error( $topic_id ) ) {
+
+		// Update counts, etc...
+		do_action( 'bbp_edit_topic', $topic_id, $forum_id, $anonymous_data, $topic_author , true /* Is edit */ );
+
+		/** Revisions *********************************************************/
+
+		// Update locks
+		update_post_meta( $topic_id, '_edit_last', bbp_get_current_user_id() );
+		delete_post_meta( $topic_id, '_edit_lock' );
+
+		// Revision Reason
+		if ( ! empty( $_POST['bbp_topic_edit_reason'] ) ) {
+			$topic_edit_reason = sanitize_text_field( $_POST['bbp_topic_edit_reason'] );
+		}
+
+		// Update revision log
+		if ( ! empty( $_POST['bbp_log_topic_edit'] ) && ( "1" === $_POST['bbp_log_topic_edit'] ) )  {
+			$revision_id = wp_save_post_revision( $topic_id );
+			if ( ! empty( $revision_id ) ) {
+				bbp_update_topic_revision_log( array(
+					'topic_id'    => $topic_id,
+					'revision_id' => $revision_id,
+					'author_id'   => bbp_get_current_user_id(),
+					'reason'      => $topic_edit_reason
+				) );
+			}
+		}
+
+		/** Move Topic ********************************************************/
+
+		// If the new forum id is not equal to the old forum id, run the
+		// bbp_move_topic action and pass the topic's forum id as the
+		// first arg and topic id as the second to update counts.
+		if ( $forum_id !== $topic->post_parent ) {
+			bbp_move_topic_handler( $topic_id, $topic->post_parent, $forum_id );
+		}
+
+		/** Additional Actions (After Save) ***********************************/
+
+		do_action( 'bbp_edit_topic_post_extras', $topic_id );
+
+		/** Redirect **********************************************************/
+
+		// Redirect to
+		$redirect_to = bbp_get_redirect_to();
+
+		// View all?
+		$view_all = bbp_get_view_all( 'edit_others_replies' );
+
+		// Get the topic URL
+		$topic_url = bbp_get_topic_permalink( $topic_id, $redirect_to );
+
+		// Add view all?
+		if ( ! empty( $view_all ) ) {
+			$topic_url = bbp_add_view_all( $topic_url );
+		}
+
+		// Allow to be filtered
+		$topic_url = apply_filters( 'bbp_edit_topic_redirect_to', $topic_url, $view_all, $redirect_to );
+
+		/** Successful Edit ***************************************************/
+
+		// Redirect back to new topic
+		bbp_redirect( $topic_url );
+
+	/** Errors ****************************************************************/
+
+	} else {
+		$append_error = ( is_wp_error( $topic_id ) && $topic_id->get_error_message() ) ? $topic_id->get_error_message() . ' ' : '';
+		bbp_add_error( 'bbp_topic_error', __( '<strong>Error</strong>: The following problem(s) have been found with your topic:' . $append_error . 'Please try again.', 'bbpress' ) );
+	}
+}
+
+/**
+ * Handle all the extra meta stuff from posting a new topic
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param int $forum_id Optional. Forum id
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ * @param int $author_id Author id
+ * @param bool $is_edit Optional. Is the post being edited? Defaults to false.
+ */
+function bbp_update_topic( $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $author_id = 0, $is_edit = false ) {
+
+	// Validate the ID's passed from 'bbp_new_topic' action
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$forum_id = bbp_get_forum_id( $forum_id );
+
+	// Bail if there is no topic
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// Check author_id
+	if ( empty( $author_id ) ) {
+		$author_id = bbp_get_current_user_id();
+	}
+
+	// Forum/Topic meta (early, for use in downstream functions)
+	bbp_update_topic_forum_id( $topic_id, $forum_id );
+	bbp_update_topic_topic_id( $topic_id, $topic_id );
+
+	// Get the topic types
+	$topic_types = bbp_get_topic_types( $topic_id );
+
+	// Sticky check after 'bbp_new_topic' action so forum ID meta is set
+	if ( ! empty( $_POST['bbp_stick_topic'] ) && in_array( $_POST['bbp_stick_topic'], array_keys( $topic_types ), true ) ) {
+
+		// What's the caps?
+		if ( current_user_can( 'moderate', $topic_id ) ) {
+
+			// What's the haps?
+			switch ( $_POST['bbp_stick_topic'] ) {
+
+				// Sticky in this forum
+				case 'stick'   :
+					bbp_stick_topic( $topic_id );
+					break;
+
+				// Super sticky in all forums
+				case 'super'   :
+					bbp_stick_topic( $topic_id, true );
+					break;
+
+				// Unsticky from everywhere
+				case 'unstick' :
+				default        :
+					bbp_unstick_topic( $topic_id );
+					break;
+			}
+		}
+	}
+
+	// If anonymous post, store name, email, website and ip in post_meta.
+	if ( ! empty( $anonymous_data ) ) {
+
+		// Update anonymous meta data (not cookies)
+		bbp_update_anonymous_post_author( $topic_id, $anonymous_data, bbp_get_topic_post_type() );
+
+		// Set transient for throttle check (only on new, not edit)
+		if ( empty( $is_edit ) ) {
+			set_transient( '_bbp_' . bbp_current_author_ip() . '_last_posted', time(), HOUR_IN_SECONDS );
+		}
+	}
+
+	// Handle Subscription Checkbox
+	if ( bbp_is_subscriptions_active() && ! empty( $author_id ) ) {
+
+		// Check if subscribed
+		$subscribed = bbp_is_user_subscribed( $author_id, $topic_id );
+
+		// Check for action
+		$subscheck  = ( ! empty( $_POST['bbp_topic_subscription'] ) && ( 'bbp_subscribe' === $_POST['bbp_topic_subscription'] ) )
+			? true
+			: false;
+
+		// Subscribed and unsubscribing
+		if ( ( true === $subscribed ) && ( false === $subscheck ) ) {
+			bbp_remove_user_subscription( $author_id, $topic_id );
+
+		// Not subscribed and subscribing
+		} elseif ( ( false === $subscribed ) && ( true === $subscheck ) ) {
+			bbp_add_user_subscription( $author_id, $topic_id );
+		}
+	}
+
+	// Update associated topic values if this is a new topic
+	if ( empty( $is_edit ) ) {
+
+		// Update poster activity time
+		bbp_update_user_last_posted( $author_id );
+
+		// Update poster IP
+		update_post_meta( $topic_id, '_bbp_author_ip', bbp_current_author_ip(), false );
+
+		// Last active time
+		$last_active = get_post_field( 'post_date', $topic_id );
+
+		// Reply topic meta
+		bbp_update_topic_last_reply_id      ( $topic_id, 0            );
+		bbp_update_topic_last_active_id     ( $topic_id, $topic_id    );
+		bbp_update_topic_last_active_time   ( $topic_id, $last_active );
+		bbp_update_topic_reply_count        ( $topic_id, 0            );
+		bbp_update_topic_reply_count_hidden ( $topic_id, 0            );
+		bbp_update_topic_voice_count        ( $topic_id               );
+
+		// Walk up ancestors and do the dirty work
+		bbp_update_topic_walker( $topic_id, $last_active, $forum_id, 0, false );
+	}
+
+	// Bump the custom query cache
+	wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
+}
+
+/**
+ * Walks up the post_parent tree from the current topic_id, and updates the
+ * meta data of forums above it. This calls several functions that all run
+ * manual queries against the database to get their results. As such, this
+ * function can be costly to run but is necessary to keep everything accurate.
+ *
+ * @since 2.0.0 bbPress (r2800)
+ *
+ * @param int $topic_id Topic id
+ * @param string $last_active_time Optional. Last active time
+ * @param int $forum_id Optional. Forum id
+ * @param int $reply_id Optional. Reply id
+ * @param bool $refresh Reset all the previous parameters? Defaults to true.
+ */
+function bbp_update_topic_walker( $topic_id, $last_active_time = '', $forum_id = 0, $reply_id = 0, $refresh = true ) {
+
+	// Validate topic_id
+	$topic_id  = bbp_get_topic_id( $topic_id );
+
+	// Define local variable(s)
+	$active_id = 0;
+
+	// Topic was passed
+	if ( ! empty( $topic_id ) ) {
+
+		// Get the forum ID if none was passed
+		if ( empty( $forum_id )  ) {
+			$forum_id = bbp_get_topic_forum_id( $topic_id );
+		}
+
+		// Set the active_id based on topic_id/reply_id
+		$active_id = empty( $reply_id ) ? $topic_id : $reply_id;
+	}
+
+	// Get topic ancestors
+	$ancestors = array_values( array_unique( array_merge( array( $forum_id ), (array) get_post_ancestors( $topic_id ) ) ) );
+
+	// Topic status
+	$topic_status = get_post_status( $topic_id );
+
+	// If we want a full refresh, unset any of the possibly passed variables
+	if ( true === $refresh ) {
+		$forum_id = $topic_id = $reply_id = $active_id = $last_active_time = 0;
+		$topic_status = bbp_get_public_status_id();
+	}
+
+	// Loop through ancestors
+	if ( ! empty( $ancestors ) ) {
+		foreach ( $ancestors as $ancestor ) {
+
+			// If ancestor is a forum, update counts
+			if ( bbp_is_forum( $ancestor ) ) {
+
+				// Get the forum
+				$forum = bbp_get_forum( $ancestor );
+
+				// Update the forum
+				bbp_update_forum( array(
+					'forum_id'           => $forum->ID,
+					'post_parent'        => $forum->post_parent,
+					'last_topic_id'      => $topic_id,
+					'last_reply_id'      => $reply_id,
+					'last_active_id'     => $active_id,
+					'last_active_time'   => $last_active_time,
+					'last_active_status' => $topic_status
+				) );
+			}
+		}
+	}
+}
+
+/**
+ * Handle the moving of a topic from one forum to another. This includes walking
+ * up the old and new branches and updating the counts.
+ *
+ * @since 2.0.0 bbPress (r2907)
+ *
+ * @param int $topic_id     The topic id.
+ * @param int $old_forum_id Old forum id.
+ * @param int $new_forum_id New forum id.
+ */
+function bbp_move_topic_handler( $topic_id, $old_forum_id, $new_forum_id ) {
+
+	// Validate parameters
+	$topic_id     = bbp_get_topic_id( $topic_id     );
+	$old_forum_id = bbp_get_forum_id( $old_forum_id );
+	$new_forum_id = bbp_get_forum_id( $new_forum_id );
+
+	// Clean old and new forum caches before proceeding, to ensure subsequent
+	// calls to forum objects are using updated data.
+	clean_post_cache( $old_forum_id );
+	clean_post_cache( $new_forum_id );
+
+	// Update topic forum's ID
+	bbp_update_topic_forum_id( $topic_id, $new_forum_id );
+
+	// Update topic post parent with the new forum ID
+	wp_update_post( array(
+		'ID'          => $topic_id,
+		'post_parent' => $new_forum_id,
+	) );
+
+	/** Stickies **************************************************************/
+
+	// Get forum stickies
+	$old_stickies = bbp_get_stickies( $old_forum_id );
+
+	// Only proceed if stickies are found
+	if ( ! empty( $old_stickies ) ) {
+
+		// Define local variables
+		$updated_stickies = array();
+
+		// Loop through stickies of forum and add misses to the updated array
+		foreach ( (array) $old_stickies as $sticky_topic_id ) {
+			if ( $topic_id !== $sticky_topic_id ) {
+				$updated_stickies[] = $sticky_topic_id;
+			}
+		}
+
+		// If stickies are different, update or delete them
+		if ( $updated_stickies !== $old_stickies ) {
+
+			// No more stickies so delete the meta
+			if ( empty( $updated_stickies ) ) {
+				delete_post_meta( $old_forum_id, '_bbp_sticky_topics' );
+
+			// Still stickies so update the meta
+			} else {
+				update_post_meta( $old_forum_id, '_bbp_sticky_topics', $updated_stickies );
+			}
+
+			// Topic was sticky, so restick in new forum
+			bbp_stick_topic( $topic_id );
+		}
+	}
+
+	/** Topic Replies *********************************************************/
+
+	// Get the topics replies
+	$replies = bbp_get_all_child_ids( $topic_id, bbp_get_reply_post_type() );
+
+	// Update the forum_id of all replies in the topic
+	foreach ( $replies as $reply_id ) {
+		bbp_update_reply_forum_id( $reply_id, $new_forum_id );
+	}
+
+	/** Old forum_id **********************************************************/
+
+	// Get topic ancestors
+	$old_forum_ancestors = array_values( array_unique( array_merge( array( $old_forum_id ), (array) get_post_ancestors( $old_forum_id ) ) ) );
+
+	// Public counts
+	if ( bbp_is_topic_public( $topic_id ) ) {
+
+		// Update old forum counts.
+		bbp_decrease_forum_topic_count( $old_forum_id );
+
+		// Update new forum counts.
+		bbp_increase_forum_topic_count( $new_forum_id );
+
+	// Non-public counts
+	} else {
+
+		// Update old forum counts.
+		bbp_decrease_forum_topic_count_hidden( $old_forum_id );
+
+		// Update new forum counts.
+		bbp_increase_forum_topic_count_hidden( $new_forum_id );
+	}
+
+	// Get reply counts.
+	$public_reply_count = bbp_get_public_child_count( $topic_id, bbp_get_reply_post_type() );
+	$hidden_reply_count = bbp_get_non_public_child_count( $topic_id, bbp_get_reply_post_type() );
+
+	// Bump reply counts.
+	bbp_bump_forum_reply_count( $old_forum_id, -$public_reply_count );
+	bbp_bump_forum_reply_count( $new_forum_id, $public_reply_count );
+	bbp_bump_forum_reply_count_hidden( $old_forum_id, -$hidden_reply_count );
+	bbp_bump_forum_reply_count_hidden( $new_forum_id, $hidden_reply_count );
+
+	// Loop through ancestors and update them
+	if ( ! empty( $old_forum_ancestors ) ) {
+		foreach ( $old_forum_ancestors as $ancestor ) {
+			if ( bbp_is_forum( $ancestor ) ) {
+				bbp_update_forum( array(
+					'forum_id' => $ancestor,
+				) );
+			}
+		}
+	}
+
+	/** New forum_id **********************************************************/
+
+	// Make sure we're not walking twice
+	if ( ! in_array( $new_forum_id, $old_forum_ancestors, true ) ) {
+
+		// Get topic ancestors
+		$new_forum_ancestors = array_values( array_unique( array_merge( array( $new_forum_id ), (array) get_post_ancestors( $new_forum_id ) ) ) );
+
+		// Make sure we're not walking twice
+		$new_forum_ancestors = array_diff( $new_forum_ancestors, $old_forum_ancestors );
+
+		// Loop through ancestors and update them
+		if ( ! empty( $new_forum_ancestors ) ) {
+			foreach ( $new_forum_ancestors as $ancestor ) {
+				if ( bbp_is_forum( $ancestor ) ) {
+					bbp_update_forum( array(
+						'forum_id' => $ancestor,
+					) );
+				}
+			}
+		}
+	}
+}
+
+/**
+ * Merge topic handler
+ *
+ * Handles the front end merge topic submission
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_merge_topic_handler( $action = '' ) {
+
+	// Bail if action is not bbp-merge-topic
+	if ( 'bbp-merge-topic' !== $action ) {
+		return;
+	}
+
+	// Define local variable(s)
+	$source_topic_id = $destination_topic_id = 0;
+	$source_topic = $destination_topic = 0;
+	$subscribers = $favoriters = $replies = array();
+
+	/** Source Topic **********************************************************/
+
+	// Topic id
+	if ( empty( $_POST['bbp_topic_id'] ) ) {
+		bbp_add_error( 'bbp_merge_topic_source_id', __( '<strong>Error</strong>: Topic ID not found.', 'bbpress' ) );
+	} else {
+		$source_topic_id = (int) $_POST['bbp_topic_id'];
+	}
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'bbp-merge-topic_' . $source_topic_id ) ) {
+		bbp_add_error( 'bbp_merge_topic_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+
+	// Source topic not found
+	} elseif ( ! $source_topic = bbp_get_topic( $source_topic_id ) ) {
+		bbp_add_error( 'bbp_merge_topic_source_not_found', __( '<strong>Error</strong>: The topic you want to merge was not found.', 'bbpress' ) );
+		return;
+	}
+
+	// Cannot edit source topic
+	if ( ! current_user_can( 'edit_topic', $source_topic->ID ) ) {
+		bbp_add_error( 'bbp_merge_topic_source_permission', __( '<strong>Error</strong>: You do not have permission to edit the source topic.', 'bbpress' ) );
+		return;
+	}
+
+	/** Destination Topic *****************************************************/
+
+	// Topic id
+	if ( empty( $_POST['bbp_destination_topic'] ) ) {
+		bbp_add_error( 'bbp_merge_topic_destination_id', __( '<strong>Error</strong>: Destination topic ID not found.', 'bbpress' ) );
+	} else {
+		$destination_topic_id = (int) $_POST['bbp_destination_topic'];
+	}
+
+	// Destination topic not found
+	if ( ! $destination_topic = bbp_get_topic( $destination_topic_id ) ) {
+		bbp_add_error( 'bbp_merge_topic_destination_not_found', __( '<strong>Error</strong>: The topic you want to merge to was not found.', 'bbpress' ) );
+	}
+
+	// Cannot edit destination topic
+	if ( ! current_user_can( 'edit_topic', $destination_topic->ID ) ) {
+		bbp_add_error( 'bbp_merge_topic_destination_permission', __( '<strong>Error</strong>: You do not have permission to edit the destination topic.', 'bbpress' ) );
+	}
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors *************************************************************/
+
+	// Update counts, etc...
+	do_action( 'bbp_merge_topic', $destination_topic->ID, $source_topic->ID );
+
+	/** Date Check ************************************************************/
+
+	// Check if the destination topic is older than the source topic
+	if ( strtotime( $source_topic->post_date ) < strtotime( $destination_topic->post_date ) ) {
+
+		// Set destination topic post_date to 1 second before source topic
+		$destination_post_date = date( 'Y-m-d H:i:s', strtotime( $source_topic->post_date ) - 1 );
+
+		// Update destination topic
+		wp_update_post( array(
+			'ID'            => $destination_topic_id,
+			'post_date'     => $destination_post_date,
+			'post_date_gmt' => get_gmt_from_date( $destination_post_date )
+		) );
+	}
+
+	/** Engagements ***********************************************************/
+
+	// Get engagements from source topic
+	$engagements = bbp_get_topic_engagements( $source_topic->ID );
+
+	// Maybe migrate engagements
+	if ( ! empty( $engagements ) ) {
+		foreach ( $engagements as $engager ) {
+			bbp_add_user_engagement( $engager, $destination_topic->ID );
+		}
+	}
+
+	/** Subscriptions *********************************************************/
+
+	// Get subscribers from source topic
+	$subscribers = bbp_get_subscribers( $source_topic->ID );
+
+	// Maybe migrate subscriptions
+	if ( ! empty( $subscribers ) && ! empty( $_POST['bbp_topic_subscribers'] ) && ( '1' === $_POST['bbp_topic_subscribers'] ) ) {
+		foreach ( $subscribers as $subscriber ) {
+			bbp_add_user_subscription( $subscriber, $destination_topic->ID );
+		}
+	}
+
+	/** Favorites *************************************************************/
+
+	// Get favoriters from source topic
+	$favoriters = bbp_get_topic_favoriters( $source_topic->ID );
+
+	// Maybe migrate favorites
+	if ( ! empty( $favoriters ) && ! empty( $_POST['bbp_topic_favoriters'] ) && ( '1' === $_POST['bbp_topic_favoriters'] ) ) {
+		foreach ( $favoriters as $favoriter ) {
+			bbp_add_user_favorite( $favoriter, $destination_topic->ID );
+		}
+	}
+
+	/** Tags ******************************************************************/
+
+	// Get the source topic tags
+	$source_topic_tags = wp_get_post_terms( $source_topic->ID, bbp_get_topic_tag_tax_id(), array( 'fields' => 'names' ) );
+
+	// Tags to possibly merge
+	if ( ! empty( $source_topic_tags ) && ! is_wp_error( $source_topic_tags ) ) {
+
+		// Shift the tags if told to
+		if ( ! empty( $_POST['bbp_topic_tags'] ) && ( "1" === $_POST['bbp_topic_tags'] ) ) {
+			wp_set_post_terms( $destination_topic->ID, $source_topic_tags, bbp_get_topic_tag_tax_id(), true );
+		}
+
+		// Delete the tags from the source topic
+		wp_delete_object_term_relationships( $source_topic->ID, bbp_get_topic_tag_tax_id() );
+	}
+
+	/** Source Topic **********************************************************/
+
+	// Status
+	bbp_open_topic( $source_topic->ID );
+
+	// Sticky
+	bbp_unstick_topic( $source_topic->ID );
+
+	// Delete source topic's last & count meta data
+	delete_post_meta( $source_topic->ID, '_bbp_last_reply_id'      );
+	delete_post_meta( $source_topic->ID, '_bbp_last_active_id'     );
+	delete_post_meta( $source_topic->ID, '_bbp_last_active_time'   );
+	delete_post_meta( $source_topic->ID, '_bbp_voice_count'        );
+	delete_post_meta( $source_topic->ID, '_bbp_reply_count'        );
+	delete_post_meta( $source_topic->ID, '_bbp_reply_count_hidden' );
+
+	// Delete source topics user relationships
+	delete_post_meta( $source_topic->ID, '_bbp_favorite'     );
+	delete_post_meta( $source_topic->ID, '_bbp_subscription' );
+	delete_post_meta( $source_topic->ID, '_bbp_engagement'   );
+
+	// Get the replies of the source topic
+	$replies = (array) get_posts( array(
+		'post_parent'    => $source_topic->ID,
+		'post_type'      => bbp_get_reply_post_type(),
+		'posts_per_page' => -1,
+		'order'          => 'ASC'
+	) );
+
+	// Prepend the source topic to its replies array for processing
+	array_unshift( $replies, $source_topic );
+
+	if ( ! empty( $replies ) ) {
+
+		/** Merge Replies *****************************************************/
+
+		// Change the post_parent of each reply to the destination topic id
+		foreach ( $replies as $reply ) {
+
+			// Update the reply
+			wp_update_post( array(
+				'ID'          => $reply->ID,
+				'post_title'  => '',
+				'post_name'   => false,
+				'post_type'   => bbp_get_reply_post_type(),
+				'post_parent' => $destination_topic->ID,
+				'guid'        => ''
+			) );
+
+			// Adjust reply meta values
+			bbp_update_reply_topic_id( $reply->ID, $destination_topic->ID                           );
+			bbp_update_reply_forum_id( $reply->ID, bbp_get_topic_forum_id( $destination_topic->ID ) );
+
+			// Update the reply position
+			bbp_update_reply_position( $reply->ID );
+
+			// Do additional actions per merged reply
+			do_action( 'bbp_merged_topic_reply', $reply->ID, $destination_topic->ID );
+		}
+	}
+
+	/** Successful Merge ******************************************************/
+
+	// Update topic's last meta data
+	bbp_update_topic_last_reply_id   ( $destination_topic->ID );
+	bbp_update_topic_last_active_id  ( $destination_topic->ID );
+	bbp_update_topic_last_active_time( $destination_topic->ID );
+
+	// Send the post parent of the source topic as it has been shifted
+	// (possibly to a new forum) so we need to update the counts of the
+	// old forum as well as the new one
+	do_action( 'bbp_merged_topic', $destination_topic->ID, $source_topic->ID, $source_topic->post_parent );
+
+	// Redirect back to new topic
+	bbp_redirect( bbp_get_topic_permalink( $destination_topic->ID ) );
+}
+
+/**
+ * Fix counts on topic merge
+ *
+ * When a topic is merged, update the counts of source and destination topic
+ * and their forums.
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param int $destination_topic_id Destination topic id
+ * @param int $source_topic_id Source topic id
+ * @param int $source_topic_forum_id Source topic's forum id
+ */
+function bbp_merge_topic_count( $destination_topic_id, $source_topic_id, $source_topic_forum_id ) {
+
+	/** Source Topic **********************************************************/
+
+	// Forum Topic Counts
+	bbp_update_forum_topic_count( $source_topic_forum_id );
+
+	// Forum Reply Counts
+	bbp_update_forum_reply_count( $source_topic_forum_id );
+
+	/** Destination Topic *****************************************************/
+
+	// Topic Reply Counts
+	bbp_update_topic_reply_count( $destination_topic_id );
+
+	// Topic Hidden Reply Counts
+	bbp_update_topic_reply_count_hidden( $destination_topic_id );
+
+	// Topic Voice Counts
+	bbp_update_topic_voice_count( $destination_topic_id );
+
+	do_action( 'bbp_merge_topic_count', $destination_topic_id, $source_topic_id, $source_topic_forum_id );
+}
+
+/**
+ * Split topic handler
+ *
+ * Handles the front end split topic submission
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_split_topic_handler( $action = '' ) {
+
+	// Bail if action is not 'bbp-split-topic'
+	if ( 'bbp-split-topic' !== $action ) {
+		return;
+	}
+
+	// Prevent debug notices
+	$from_reply_id = $destination_topic_id = 0;
+	$destination_topic_title = '';
+	$destination_topic = $from_reply = $source_topic = '';
+	$split_option = false;
+
+	/** Split Reply ***********************************************************/
+
+	if ( empty( $_POST['bbp_reply_id'] ) ) {
+		bbp_add_error( 'bbp_split_topic_reply_id', __( '<strong>Error</strong>: A reply ID is required.', 'bbpress' ) );
+	} else {
+		$from_reply_id = (int) $_POST['bbp_reply_id'];
+	}
+
+	$from_reply = bbp_get_reply( $from_reply_id );
+
+	// Reply exists
+	if ( empty( $from_reply ) ) {
+		bbp_add_error( 'bbp_split_topic_r_not_found', __( '<strong>Error</strong>: The reply you want to split from was not found.', 'bbpress' ) );
+	}
+
+	/** Topic to Split ********************************************************/
+
+	// Get the topic being split
+	$source_topic = bbp_get_topic( $from_reply->post_parent );
+
+	// No topic
+	if ( empty( $source_topic ) ) {
+		bbp_add_error( 'bbp_split_topic_source_not_found', __( '<strong>Error</strong>: The topic you want to split was not found.', 'bbpress' ) );
+	}
+
+	// Nonce check failed
+	if ( ! bbp_verify_nonce_request( 'bbp-split-topic_' . $source_topic->ID ) ) {
+		bbp_add_error( 'bbp_split_topic_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Use cannot edit topic
+	if ( ! current_user_can( 'edit_topic', $source_topic->ID ) ) {
+		bbp_add_error( 'bbp_split_topic_source_permission', __( '<strong>Error</strong>: You do not have permission to edit the source topic.', 'bbpress' ) );
+	}
+
+	// How to Split
+	if ( ! empty( $_POST['bbp_topic_split_option'] ) ) {
+		$split_option = sanitize_key( $_POST['bbp_topic_split_option'] );
+	}
+
+	// Invalid split option
+	if ( empty( $split_option ) || ! in_array( $split_option, array( 'existing', 'reply' ), true ) ) {
+		bbp_add_error( 'bbp_split_topic_option', __( '<strong>Error</strong>: You need to choose a valid split option.', 'bbpress' ) );
+
+	// Valid Split Option
+	} else {
+
+		// What kind of split
+		switch ( $split_option ) {
+
+			// Into an existing topic
+			case 'existing' :
+
+				// Get destination topic id
+				if ( empty( $_POST['bbp_destination_topic'] ) ) {
+					bbp_add_error( 'bbp_split_topic_destination_id', __( '<strong>Error</strong>: A topic ID is required.', 'bbpress' ) );
+				} else {
+					$destination_topic_id = (int) $_POST['bbp_destination_topic'];
+				}
+
+				// Get the destination topic
+				$destination_topic = bbp_get_topic( $destination_topic_id );
+
+				// No destination topic
+				if ( empty( $destination_topic ) ) {
+					bbp_add_error( 'bbp_split_topic_destination_not_found', __( '<strong>Error</strong>: The topic you want to split to was not found.', 'bbpress' ) );
+				}
+
+				// User cannot edit the destination topic
+				if ( ! current_user_can( 'edit_topic', $destination_topic->ID ) ) {
+					bbp_add_error( 'bbp_split_topic_destination_permission', __( '<strong>Error</strong>: You do not have permission to edit the destination topic.', 'bbpress' ) );
+				}
+
+				break;
+
+			// Split at reply into a new topic
+			case 'reply' :
+			default :
+
+				// User needs to be able to publish topics
+				if ( current_user_can( 'publish_topics' ) ) {
+
+					// Use the new title that was passed
+					if ( ! empty( $_POST['bbp_topic_split_destination_title'] ) ) {
+						$destination_topic_title = sanitize_text_field( $_POST['bbp_topic_split_destination_title'] );
+
+					// Use the source topic title
+					} else {
+						$destination_topic_title = $source_topic->post_title;
+					}
+
+					// Update the topic
+					$destination_topic_id = wp_update_post( array(
+						'ID'          => $from_reply->ID,
+						'post_title'  => $destination_topic_title,
+						'post_name'   => false,
+						'post_type'   => bbp_get_topic_post_type(),
+						'post_parent' => $source_topic->post_parent,
+						'menu_order'  => 0,
+						'guid'        => ''
+					) );
+					$destination_topic = bbp_get_topic( $destination_topic_id );
+
+					// Make sure the new topic knows its a topic
+					bbp_update_topic_topic_id( $from_reply->ID );
+
+					// Shouldn't happen
+					if ( false === $destination_topic_id || is_wp_error( $destination_topic_id ) || empty( $destination_topic ) ) {
+						bbp_add_error( 'bbp_split_topic_destination_reply', __( '<strong>Error</strong>: There was a problem converting the reply into the topic. Please try again.', 'bbpress' ) );
+					}
+
+				// User cannot publish posts
+				} else {
+					bbp_add_error( 'bbp_split_topic_destination_permission', __( '<strong>Error</strong>: You do not have permission to create new topics. The reply could not be converted into a topic.', 'bbpress' ) );
+				}
+
+				break;
+		}
+	}
+
+	// Bail if there are errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	/** No Errors - Do the Spit ***********************************************/
+
+	// Update counts, etc...
+	do_action( 'bbp_pre_split_topic', $from_reply->ID, $source_topic->ID, $destination_topic->ID );
+
+	/** Date Check ************************************************************/
+
+	// Check if the destination topic is older than the from reply
+	if ( strtotime( $from_reply->post_date ) < strtotime( $destination_topic->post_date ) ) {
+
+		// Set destination topic post_date to 1 second before from reply
+		$destination_post_date = date( 'Y-m-d H:i:s', strtotime( $from_reply->post_date ) - 1 );
+
+		// Update destination topic
+		wp_update_post( array(
+			'ID'            => $destination_topic_id,
+			'post_date'     => $destination_post_date,
+			'post_date_gmt' => get_gmt_from_date( $destination_post_date )
+		) );
+	}
+
+	/** Subscriptions *********************************************************/
+
+	// Copy the subscribers
+	if ( ! empty( $_POST['bbp_topic_subscribers'] ) && "1" === $_POST['bbp_topic_subscribers'] && bbp_is_subscriptions_active() ) {
+
+		// Get the subscribers
+		$subscribers = bbp_get_subscribers( $source_topic->ID );
+
+		if ( ! empty( $subscribers ) ) {
+
+			// Add subscribers to new topic
+			foreach ( (array) $subscribers as $subscriber ) {
+				bbp_add_user_subscription( $subscriber, $destination_topic->ID );
+			}
+		}
+	}
+
+	/** Favorites *************************************************************/
+
+	// Copy the favoriters if told to
+	if ( ! empty( $_POST['bbp_topic_favoriters'] ) && ( "1" === $_POST['bbp_topic_favoriters'] ) ) {
+
+		// Get the favoriters
+		$favoriters = bbp_get_topic_favoriters( $source_topic->ID );
+
+		if ( ! empty( $favoriters ) ) {
+
+			// Add the favoriters to new topic
+			foreach ( (array) $favoriters as $favoriter ) {
+				bbp_add_user_favorite( $favoriter, $destination_topic->ID );
+			}
+		}
+	}
+
+	/** Tags ******************************************************************/
+
+	// Copy the tags if told to
+	if ( ! empty( $_POST['bbp_topic_tags'] ) && ( "1" === $_POST['bbp_topic_tags'] ) ) {
+
+		// Get the source topic tags
+		$source_topic_tags = wp_get_post_terms( $source_topic->ID, bbp_get_topic_tag_tax_id(), array( 'fields' => 'names' ) );
+
+		if ( ! empty( $source_topic_tags ) ) {
+			wp_set_post_terms( $destination_topic->ID, $source_topic_tags, bbp_get_topic_tag_tax_id(), true );
+		}
+	}
+
+	/** Split Replies *********************************************************/
+
+	// get_posts() is not used because it doesn't allow us to use '>='
+	// comparision without a filter.
+	$bbp_db  = bbp_db();
+	$query   = $bbp_db->prepare( "SELECT * FROM {$bbp_db->posts} WHERE {$bbp_db->posts}.post_date >= %s AND {$bbp_db->posts}.post_parent = %d AND {$bbp_db->posts}.post_type = %s ORDER BY {$bbp_db->posts}.post_date ASC", $from_reply->post_date, $source_topic->ID, bbp_get_reply_post_type() );
+	$replies = (array) $bbp_db->get_results( $query );
+
+	// Make sure there are replies to loop through
+	if ( ! empty( $replies ) && ! is_wp_error( $replies ) ) {
+
+		// Save reply ids
+		$reply_ids = array();
+
+		// Change the post_parent of each reply to the destination topic id
+		foreach ( $replies as $reply ) {
+
+			// Update the reply
+			wp_update_post( array(
+				'ID'          => $reply->ID,
+				'post_title'  => '',
+				'post_name'   => false, // will be automatically generated
+				'post_parent' => $destination_topic->ID,
+				'guid'        => ''
+			) );
+
+			// Gather reply ids
+			$reply_ids[] = $reply->ID;
+
+			// Adjust reply meta values
+			bbp_update_reply_topic_id( $reply->ID, $destination_topic->ID                           );
+			bbp_update_reply_forum_id( $reply->ID, bbp_get_topic_forum_id( $destination_topic->ID ) );
+
+			// Adjust reply position
+			bbp_update_reply_position( $reply->ID );
+
+			// Adjust reply to values
+			$reply_to = bbp_get_reply_to( $reply->ID );
+
+			// Not a reply to a reply that moved over
+			if ( ! in_array( $reply_to, $reply_ids, true ) ) {
+				bbp_update_reply_to( $reply->ID, 0 );
+			}
+
+			// New topic from reply can't be a reply to
+			if ( ( $from_reply->ID === $destination_topic->ID ) && ( $from_reply->ID === $reply_to ) ) {
+				bbp_update_reply_to( $reply->ID, 0 );
+			}
+
+			// Do additional actions per split reply
+			do_action( 'bbp_split_topic_reply', $reply->ID, $destination_topic->ID );
+		}
+
+		// Remove reply to from new topic
+		if ( $from_reply->ID === $destination_topic->ID ) {
+			delete_post_meta( $from_reply->ID, '_bbp_reply_to' );
+		}
+
+		// Set the last reply ID and freshness
+		$last_reply_id = $reply->ID;
+		$freshness     = $reply->post_date;
+
+	// Set the last reply ID and freshness to the from_reply
+	} else {
+		$last_reply_id = $from_reply->ID;
+		$freshness     = $from_reply->post_date;
+	}
+
+	// It is a new topic and we need to set some default metas to make
+	// the topic display in bbp_has_topics() list
+	if ( 'reply' === $split_option ) {
+		bbp_update_topic_last_reply_id   ( $destination_topic->ID, $last_reply_id );
+		bbp_update_topic_last_active_id  ( $destination_topic->ID, $last_reply_id );
+		bbp_update_topic_last_active_time( $destination_topic->ID, $freshness     );
+	}
+
+	// Update source topic ID last active
+	bbp_update_topic_last_reply_id   ( $source_topic->ID );
+	bbp_update_topic_last_active_id  ( $source_topic->ID );
+	bbp_update_topic_last_active_time( $source_topic->ID );
+
+	/** Successful Split ******************************************************/
+
+	// Update counts, etc...
+	do_action( 'bbp_post_split_topic', $from_reply->ID, $source_topic->ID, $destination_topic->ID );
+
+	// Redirect back to the topic
+	bbp_redirect( bbp_get_topic_permalink( $destination_topic->ID ) );
+}
+
+/**
+ * Fix counts on topic split
+ *
+ * When a topic is split, update the counts of source and destination topic
+ * and their forums.
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param int $from_reply_id From reply id
+ * @param int $source_topic_id Source topic id
+ * @param int $destination_topic_id Destination topic id
+ */
+function bbp_split_topic_count( $from_reply_id, $source_topic_id, $destination_topic_id ) {
+
+	// Forum Topic Counts
+	bbp_update_forum_topic_count( bbp_get_topic_forum_id( $destination_topic_id ) );
+
+	// Forum Reply Counts
+	bbp_update_forum_reply_count( bbp_get_topic_forum_id( $destination_topic_id ) );
+
+	// Topic Reply Counts
+	bbp_update_topic_reply_count( $source_topic_id      );
+	bbp_update_topic_reply_count( $destination_topic_id );
+
+	// Topic Hidden Reply Counts
+	bbp_update_topic_reply_count_hidden( $source_topic_id      );
+	bbp_update_topic_reply_count_hidden( $destination_topic_id );
+
+	// Topic Voice Counts
+	bbp_update_topic_voice_count( $source_topic_id      );
+	bbp_update_topic_voice_count( $destination_topic_id );
+
+	do_action( 'bbp_split_topic_count', $from_reply_id, $source_topic_id, $destination_topic_id );
+}
+
+/**
+ * Handles the front end tag management (renaming, merging, destroying)
+ *
+ * @since 2.0.0 bbPress (r2768)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_edit_topic_tag_handler( $action = '' ) {
+
+	// Bail if required POST actions aren't passed
+	if ( empty( $_POST['tag-id'] ) ) {
+		return;
+	}
+
+	// Setup possible get actions
+	$possible_actions = array(
+		'bbp-update-topic-tag',
+		'bbp-merge-topic-tag',
+		'bbp-delete-topic-tag'
+	);
+
+	// Bail if actions aren't meant for this function
+	if ( ! in_array( $action, $possible_actions, true ) ) {
+		return;
+	}
+
+	// Setup vars
+	$tag_id = (int) $_POST['tag-id'];
+	$tag    = get_term( $tag_id, bbp_get_topic_tag_tax_id() );
+
+	// Tag does not exist
+	if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
+		bbp_add_error( 'bbp_manage_topic_invalid_tag', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found while getting the tag: %s', 'bbpress' ), $tag->get_error_message() ) );
+		return;
+	}
+
+	// What action are we trying to perform?
+	switch ( $action ) {
+
+		// Update tag
+		case 'bbp-update-topic-tag' :
+
+			// Nonce check
+			if ( ! bbp_verify_nonce_request( 'update-tag_' . $tag_id ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_update_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+				return;
+			}
+
+			// Can user edit topic tags?
+			if ( ! current_user_can( 'edit_topic_tag', $tag_id ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_update_permission', __( '<strong>Error</strong>: You do not have permission to edit the topic tags.', 'bbpress' ) );
+				return;
+			}
+
+			// No tag name was provided
+			if ( empty( $_POST['tag-name'] ) || ! $name = $_POST['tag-name'] ) {
+				bbp_add_error( 'bbp_manage_topic_tag_update_name', __( '<strong>Error</strong>: You need to enter a tag name.', 'bbpress' ) );
+				return;
+			}
+
+			// Attempt to update the tag
+			$slug        = ! empty( $_POST['tag-slug']        ) ? $_POST['tag-slug']        : '';
+			$description = ! empty( $_POST['tag-description'] ) ? $_POST['tag-description'] : '';
+			$tag         = wp_update_term( $tag_id, bbp_get_topic_tag_tax_id(), array(
+				'name'        => $name,
+				'slug'        => $slug,
+				'description' => $description
+			) );
+
+			// Cannot update tag
+			if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
+				bbp_add_error( 'bbp_manage_topic_tag_update_error', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found while updating the tag: %s', 'bbpress' ), $tag->get_error_message() ) );
+				return;
+			}
+
+			// Redirect
+			$redirect = get_term_link( $tag_id, bbp_get_topic_tag_tax_id() );
+
+			// Update counts, etc...
+			do_action( 'bbp_update_topic_tag', $tag_id, $tag, $name, $slug, $description );
+
+			break;
+
+		// Merge two tags
+		case 'bbp-merge-topic-tag'  :
+
+			// Nonce check
+			if ( ! bbp_verify_nonce_request( 'merge-tag_' . $tag_id ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+				return;
+			}
+
+			// Can user edit topic tags?
+			if ( ! current_user_can( 'edit_topic_tags' ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_permission', __( '<strong>Error</strong>: You do not have permission to edit the topic tags.', 'bbpress' ) );
+				return;
+			}
+
+			// No tag name was provided
+			if ( empty( $_POST['tag-existing-name'] ) || ! $name = $_POST['tag-existing-name'] ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_name', __( '<strong>Error</strong>: You need to enter a tag name.', 'bbpress' ) );
+				return;
+			}
+
+			// If term does not exist, create it
+			if ( ! $tag = term_exists( $name, bbp_get_topic_tag_tax_id() ) ) {
+				$tag = wp_insert_term( $name, bbp_get_topic_tag_tax_id() );
+			}
+
+			// Problem inserting the new term
+			if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_error', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found while merging the tags: %s', 'bbpress' ), $tag->get_error_message() ) );
+				return;
+			}
+
+			// Merging in to...
+			$to_tag = $tag['term_id'];
+
+			// Attempting to merge a tag into itself
+			if ( $tag_id === $to_tag ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_same', __( '<strong>Error</strong>: The tags which are being merged can not be the same.', 'bbpress' ) );
+				return;
+			}
+
+			// Delete the old term
+			$tag = wp_delete_term( $tag_id, bbp_get_topic_tag_tax_id(), array(
+				'default'       => $to_tag,
+				'force_default' => true
+			) );
+
+			// Error merging the terms
+			if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
+				bbp_add_error( 'bbp_manage_topic_tag_merge_error', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found while merging the tags: %s', 'bbpress' ), $tag->get_error_message() ) );
+				return;
+			}
+
+			// Redirect
+			$redirect = get_term_link( (int) $to_tag, bbp_get_topic_tag_tax_id() );
+
+			// Update counts, etc...
+			do_action( 'bbp_merge_topic_tag', $tag_id, $to_tag, $tag );
+
+			break;
+
+		// Delete tag
+		case 'bbp-delete-topic-tag' :
+
+			// Nonce check
+			if ( ! bbp_verify_nonce_request( 'delete-tag_' . $tag_id ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_delete_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+				return;
+			}
+
+			// Can user delete topic tags?
+			if ( ! current_user_can( 'delete_topic_tag', $tag_id ) ) {
+				bbp_add_error( 'bbp_manage_topic_tag_delete_permission', __( '<strong>Error</strong>: You do not have permission to delete the topic tags.', 'bbpress' ) );
+				return;
+			}
+
+			// Attempt to delete term
+			$tag = wp_delete_term( $tag_id, bbp_get_topic_tag_tax_id() );
+
+			// Error deleting term
+			if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
+				bbp_add_error( 'bbp_manage_topic_tag_delete_error', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found while deleting the tag: %s', 'bbpress' ), $tag->get_error_message() ) );
+				return;
+			}
+
+			// We don't have any other place to go other than home! Or we may die because of the 404 disease
+			$redirect = bbp_get_forums_url();
+
+			// Update counts, etc...
+			do_action( 'bbp_delete_topic_tag', $tag_id, $tag );
+
+			break;
+	}
+
+	/** Successful Moderation *************************************************/
+
+	// Redirect back
+	$redirect = ( ! empty( $redirect ) && ! is_wp_error( $redirect ) ) ? $redirect : home_url();
+	bbp_redirect( $redirect );
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Return an associative array of available topic statuses
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ *
+ * @return array
+ */
+function bbp_get_topic_statuses( $topic_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_statuses', array(
+		bbp_get_public_status_id()  => _x( 'Open',    'Open the topic',      'bbpress' ),
+		bbp_get_closed_status_id()  => _x( 'Closed',  'Close the topic',     'bbpress' ),
+		bbp_get_spam_status_id()    => _x( 'Spam',    'Spam the topic',      'bbpress' ),
+		bbp_get_trash_status_id()   => _x( 'Trash',   'Trash the topic',     'bbpress' ),
+		bbp_get_pending_status_id() => _x( 'Pending', 'Unapprove the topic', 'bbpress' )
+	), $topic_id );
+}
+
+/**
+ * Return an associative array of topic sticky types
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ *
+ * @return array
+ */
+function bbp_get_topic_types( $topic_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_types', array(
+		'unstick' => _x( 'Normal',       'Unstick a topic',         'bbpress' ),
+		'stick'   => _x( 'Sticky',       'Make topic sticky',       'bbpress' ),
+		'super'   => _x( 'Super Sticky', 'Make topic super sticky', 'bbpress' )
+	), $topic_id );
+}
+
+/**
+ * Return array of available topic toggle actions
+ *
+ * @since 2.6.0 bbPress (r6133)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ *
+ * @return array
+ */
+function bbp_get_topic_toggles( $topic_id = 0 ) {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_toggle_topic_actions', array(
+		'bbp_toggle_topic_close',
+		'bbp_toggle_topic_stick',
+		'bbp_toggle_topic_spam',
+		'bbp_toggle_topic_trash',
+		'bbp_toggle_topic_approve'
+	), $topic_id );
+}
+
+/**
+ * Return array of public topic statuses.
+ *
+ * @since 2.6.0 bbPress (r6383)
+ *
+ * @return array
+ */
+function bbp_get_public_topic_statuses() {
+	$statuses = array(
+		bbp_get_public_status_id(),
+		bbp_get_closed_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_public_topic_statuses', $statuses );
+}
+
+/**
+ * Return array of non-public topic statuses.
+ *
+ * @since 2.6.0 bbPress (r6642)
+ *
+ * @return array
+ */
+function bbp_get_non_public_topic_statuses() {
+	$statuses = array(
+		bbp_get_trash_status_id(),
+		bbp_get_spam_status_id(),
+		bbp_get_pending_status_id()
+	);
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_non_public_topic_statuses', $statuses );
+}
+
+/** Stickies ******************************************************************/
+
+/**
+ * Return sticky topics of a forum
+ *
+ * @since 2.0.0 bbPress (r2592)
+ *
+ * @param int $forum_id Optional. If not passed, super stickies are returned.
+ * @return array IDs of sticky topics of a forum or super stickies
+ */
+function bbp_get_stickies( $forum_id = 0 ) {
+
+	// Get stickies (maybe super if empty)
+	$stickies = empty( $forum_id )
+		? bbp_get_super_stickies()
+		: get_post_meta( $forum_id, '_bbp_sticky_topics', true );
+
+	// Cast as array
+	$stickies = ( empty( $stickies ) || ! is_array( $stickies ) )
+		? array()
+		: wp_parse_id_list( $stickies );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_stickies', $stickies, $forum_id );
+}
+
+/**
+ * Return topics stuck to front page of the forums
+ *
+ * @since 2.0.0 bbPress (r2592)
+ *
+ * @return array IDs of super sticky topics
+ */
+function bbp_get_super_stickies() {
+
+	// Get super stickies
+	$stickies = get_option( '_bbp_super_sticky_topics', array() );
+
+	// Cast as array
+	$stickies = ( empty( $stickies ) || ! is_array( $stickies ) )
+		? array()
+		: wp_parse_id_list( $stickies );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_super_stickies', $stickies );
+}
+
+/** Topics Actions ************************************************************/
+
+/**
+ * Handles the front end opening/closing, spamming/unspamming,
+ * sticking/unsticking and trashing/untrashing/deleting of topics
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_toggle_topic_handler( $action = '' ) {
+
+	// Bail if required GET actions aren't passed
+	if ( empty( $_GET['topic_id'] ) ) {
+		return;
+	}
+
+	// What's the topic id?
+	$topic_id = bbp_get_topic_id( (int) $_GET['topic_id'] );
+
+	// Get possible topic-handler toggles
+	$toggles = bbp_get_topic_toggles( $topic_id );
+
+	// Bail if actions aren't meant for this function
+	if ( ! in_array( $action, $toggles, true ) ) {
+		return;
+	}
+
+	// Make sure topic exists
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		bbp_add_error( 'bbp_toggle_topic_missing', __( '<strong>Error</strong>: This topic could not be found or no longer exists.', 'bbpress' ) );
+		return;
+	}
+
+	// What is the user doing here?
+	if ( ! current_user_can( 'edit_topic', $topic_id ) || ( 'bbp_toggle_topic_trash' === $action && ! current_user_can( 'delete_topic', $topic_id ) ) ) {
+		bbp_add_error( 'bbp_toggle_topic_permission', __( '<strong>Error</strong>: You do not have permission to do that.', 'bbpress' ) );
+		return;
+	}
+
+	// Sub-action?
+	$sub_action = ! empty( $_GET['sub_action'] )
+		? sanitize_key( $_GET['sub_action'] )
+		: false;
+
+	// Preliminary array
+	$post_data = array( 'ID' => $topic_id );
+
+	// Do the topic toggling
+	$retval = bbp_toggle_topic( array(
+		'id'         => $topic_id,
+		'action'     => $action,
+		'sub_action' => $sub_action,
+		'data'       => $post_data
+	) );
+
+	// Do additional topic toggle actions
+	do_action( 'bbp_toggle_topic_handler', $retval['status'], $post_data, $action );
+
+	// No errors
+	if ( ( false !== $retval['status'] ) && ! is_wp_error( $retval['status'] ) ) {
+		bbp_redirect( $retval['redirect_to'] );
+
+	// Handle errors
+	} else {
+		bbp_add_error( 'bbp_toggle_topic', $retval['message'] );
+	}
+}
+
+/**
+ * Do the actual topic toggling
+ *
+ * This function is used by `bbp_toggle_topic_handler()` to do the actual heavy
+ * lifting when it comes to toggling topic. It only really makes sense to call
+ * within that context, so if you need to call this function directly, make sure
+ * you're also doing what the handler does too.
+ *
+ * @since 2.6.0  bbPress (r6133)
+ * @access private
+ *
+ * @param array $args
+ */
+function bbp_toggle_topic( $args = array() ) {
+
+	// Parse the arguments
+	$r = bbp_parse_args( $args, array(
+		'id'         => 0,
+		'action'     => '',
+		'sub_action' => '',
+		'data'       => array()
+	) );
+
+	// Build the nonce suffix
+	$nonce_suffix = bbp_get_topic_post_type() . '_' . (int) $r['id'];
+
+	// Default return values
+	$retval = array(
+		'status'      => 0,
+		'message'     => '',
+		'redirect_to' => bbp_get_topic_permalink( $r['id'], bbp_get_redirect_to() ),
+		'view_all'    => false
+	);
+
+	// What action are we trying to perform?
+	switch ( $r['action'] ) {
+
+		// Toggle approve/unapprove
+		case 'bbp_toggle_topic_approve' :
+			check_ajax_referer( "approve-{$nonce_suffix}" );
+
+			$is_pending         = bbp_is_topic_pending( $r['id'] );
+			$retval['view_all'] = ! $is_pending;
+
+			// Toggle
+			$retval['status'] = ( true === $is_pending )
+				? bbp_approve_topic( $r['id'] )
+				: bbp_unapprove_topic( $r['id'] );
+
+			// Feedback
+			$retval['message'] = ( true === $is_pending )
+				? __( '<strong>Error</strong>: There was a problem approving the topic.',   'bbpress' )
+				: __( '<strong>Error</strong>: There was a problem unapproving the topic.', 'bbpress' );
+
+			break;
+
+		// Toggle open/close
+		case 'bbp_toggle_topic_close' :
+			check_ajax_referer( "close-{$nonce_suffix}" );
+
+			$is_open = bbp_is_topic_open( $r['id'] );
+
+			// Toggle
+			$retval['status'] = ( true === $is_open )
+				? bbp_close_topic( $r['id'] )
+				: bbp_open_topic( $r['id'] );
+
+			// Feedback
+			$retval['message'] = ( true === $is_open )
+				? __( '<strong>Error</strong>: There was a problem closing the topic.', 'bbpress' )
+				: __( '<strong>Error</strong>: There was a problem opening the topic.', 'bbpress' );
+
+			break;
+
+		// Toggle sticky/super-sticky/unstick
+		case 'bbp_toggle_topic_stick' :
+			check_ajax_referer( "stick-{$nonce_suffix}" );
+
+			$is_sticky = bbp_is_topic_sticky( $r['id'] );
+			$is_super  = false === $is_sticky && ! empty( $_GET['super'] ) && ( "1" === $_GET['super'] ) ? true : false;
+
+			// Toggle
+			$retval['status'] = ( true === $is_sticky )
+				? bbp_unstick_topic( $r['id'] )
+				: bbp_stick_topic( $r['id'], $is_super );
+
+			// Feedback
+			$retval['message'] = ( true === $is_sticky )
+				? __( '<strong>Error</strong>: There was a problem unsticking the topic.', 'bbpress' )
+				: __( '<strong>Error</strong>: There was a problem sticking the topic.',   'bbpress' );
+
+			break;
+
+		// Toggle spam
+		case 'bbp_toggle_topic_spam' :
+			check_ajax_referer( "spam-{$nonce_suffix}" );
+
+			$is_spam            = bbp_is_topic_spam( $r['id'] );
+			$retval['view_all'] = ! $is_spam;
+
+			// Toggle
+			$retval['status'] = ( true === $is_spam )
+				? bbp_unspam_topic( $r['id'] )
+				: bbp_spam_topic( $r['id'] );
+
+			// Feedback
+			$retval['message'] = ( true === $is_spam )
+				? __( '<strong>Error</strong>: There was a problem unmarking the topic as spam.', 'bbpress' )
+				: __( '<strong>Error</strong>: There was a problem marking the topic as spam.',   'bbpress' );
+
+			break;
+
+		// Toggle trash
+		case 'bbp_toggle_topic_trash' :
+
+			switch ( $r['sub_action'] ) {
+				case 'trash':
+					check_ajax_referer( "trash-{$nonce_suffix}" );
+
+					$retval['view_all']    = true;
+					$retval['status']      = wp_trash_post( $r['id'] );
+					$retval['message']     = __( '<strong>Error</strong>: There was a problem trashing the topic.', 'bbpress' );
+					$retval['redirect_to'] = current_user_can( 'view_trash' )
+						? bbp_get_topic_permalink( $r['id'] )
+						: bbp_get_forum_permalink( bbp_get_topic_forum_id( $r['id'] ) );
+
+					break;
+
+				case 'untrash':
+					check_ajax_referer( "untrash-{$nonce_suffix}" );
+
+					$retval['status']      = wp_untrash_post( $r['id'] );
+					$retval['message']     = __( '<strong>Error</strong>: There was a problem untrashing the topic.', 'bbpress' );
+					$retval['redirect_to'] = bbp_get_topic_permalink( $r['id'] );
+
+					break;
+
+				case 'delete':
+					check_ajax_referer( "delete-{$nonce_suffix}" );
+
+					$retval['status']      = wp_delete_post( $r['id'] );
+					$retval['message']     = __( '<strong>Error</strong>: There was a problem deleting the topic.', 'bbpress' );
+					$retval['redirect_to'] = bbp_get_forum_permalink( $retval['status']->post_parent );
+
+					break;
+			}
+
+			break;
+	}
+
+	// Add view all if needed
+	if ( ! empty( $retval['view_all'] ) ) {
+		$retval['redirect_to'] = bbp_add_view_all( $retval['redirect_to'], true );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_toggle_topic', $retval, $r, $args );
+}
+
+/** Favorites & Subscriptions *************************************************/
+
+/**
+ * Remove a deleted topic from all user favorites
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $topic_id Get the topic id to remove
+ */
+function bbp_remove_topic_from_all_favorites( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Bail if no topic
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// Get users
+	$users = (array) bbp_get_topic_favoriters( $topic_id );
+
+	// Users exist
+	if ( ! empty( $users ) ) {
+
+		// Loop through users
+		foreach ( $users as $user ) {
+
+			// Remove each user
+			bbp_remove_user_favorite( $user, $topic_id );
+		}
+	}
+}
+
+/**
+ * Remove a deleted topic from all user subscriptions
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $topic_id Get the topic id to remove
+ */
+function bbp_remove_topic_from_all_subscriptions( $topic_id = 0 ) {
+
+	// Subscriptions are not active
+	if ( ! bbp_is_subscriptions_active() ) {
+		return;
+	}
+
+	// Bail if no topic
+	$topic_id = bbp_get_topic_id( $topic_id );
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// Remove all users
+	return bbp_remove_object_from_all_users( $topic_id, '_bbp_subscription', 'post' );
+}
+
+/** Count Bumpers *************************************************************/
+
+/**
+ * Bump the total reply count of a topic
+ *
+ * @since 2.1.0 bbPress (r3825)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ * @param int $difference Optional. Default 1
+ * @return int Topic reply count
+ */
+function bbp_bump_topic_reply_count( $topic_id = 0, $difference = 1 ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get counts
+	$topic_id    = bbp_get_topic_id( $topic_id );
+	$reply_count = bbp_get_topic_reply_count( $topic_id, true );
+	$difference  = (int) $difference;
+	$new_count   = (int) ( $reply_count + $difference );
+
+	// Update this topic id's reply count
+	update_post_meta( $topic_id, '_bbp_reply_count', $new_count );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_topic_reply_count', $new_count, $topic_id, $difference );
+}
+
+/**
+ * Increase the total reply count of a topic by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_increase_topic_reply_count( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$reply_id = $topic_id;
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_reply_public( $reply_id ) ) {
+			bbp_increase_topic_reply_count_hidden( $topic_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_topic_reply_count( $topic_id );
+}
+
+/**
+ * Decrease the total reply count of a topic by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_decrease_topic_reply_count( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$reply_id = $topic_id;
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( ! bbp_is_reply_public( $reply_id ) ) {
+			bbp_decrease_topic_reply_count_hidden( $topic_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_topic_reply_count( $topic_id, -1 );
+}
+
+/**
+ * Bump the total hidden reply count of a topic
+ *
+ * @since 2.1.0 bbPress (r3825)
+ *
+ * @param int $topic_id   Optional. Topic id.
+ * @param int $difference Optional. Default 1
+ * @return int Topic hidden reply count
+ */
+function bbp_bump_topic_reply_count_hidden( $topic_id = 0, $difference = 1 ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Get counts
+	$topic_id    = bbp_get_topic_id( $topic_id );
+	$reply_count = bbp_get_topic_reply_count_hidden( $topic_id, true );
+	$difference  = (int) $difference;
+	$new_count   = (int) ( $reply_count + $difference );
+
+	// Update this topic id's hidden reply count
+	update_post_meta( $topic_id, '_bbp_reply_count_hidden', $new_count );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_bump_topic_reply_count_hidden', $new_count, $topic_id, $difference );
+}
+
+/**
+ * Increase the total hidden reply count of a topic by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_increase_topic_reply_count_hidden( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$reply_id = $topic_id;
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_reply_public( $reply_id ) ) {
+			bbp_increase_topic_reply_count( $topic_id );
+			return;
+		}
+	}
+
+	// Bump up
+	bbp_bump_topic_reply_count_hidden( $topic_id );
+}
+
+/**
+ * Decrease the total hidden reply count of a topic by one.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ *
+ * @return void
+ */
+function bbp_decrease_topic_reply_count_hidden( $topic_id = 0 ) {
+
+	// Bail early if no id is passed.
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// If it's a reply, get the topic id.
+	if ( bbp_is_reply( $topic_id ) ) {
+		$reply_id = $topic_id;
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+
+		// Update inverse based on item status
+		if ( bbp_is_reply_public( $reply_id ) ) {
+			bbp_decrease_topic_reply_count( $topic_id );
+			return;
+		}
+	}
+
+	// Bump down
+	bbp_bump_topic_reply_count_hidden( $topic_id, -1 );
+}
+
+/**
+ * Update counts after a topic is inserted via `bbp_insert_topic`.
+ *
+ * @since 2.6.0 bbPress (r6036)
+ *
+ * @param int $topic_id The topic id.
+ * @param int $forum_id The forum id.
+ *
+ * @return void
+ */
+function bbp_insert_topic_update_counts( $topic_id = 0, $forum_id = 0 ) {
+
+	// If the topic is public, update the forum topic counts.
+	if ( bbp_is_topic_public( $topic_id ) ) {
+		bbp_increase_forum_topic_count( $forum_id );
+
+	// If the topic isn't public only update the forum topic hidden count.
+	} else {
+		bbp_increase_forum_topic_count_hidden( $forum_id );
+	}
+}
+
+/** Topic Updaters ************************************************************/
+
+/**
+ * Update the topic's forum id
+ *
+ * @since 2.0.0 bbPress (r2855)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @param int $forum_id Optional. Forum id
+ * @return int Forum id
+ */
+function bbp_update_topic_forum_id( $topic_id = 0, $forum_id = 0 ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Forum ID fallback
+	if ( empty( $forum_id ) ) {
+		$forum_id = get_post_field( 'post_parent', $topic_id );
+	}
+
+	// Update the forum ID
+	$forum_id = bbp_update_forum_id( $topic_id, $forum_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_forum_id', $forum_id, $topic_id );
+}
+
+/**
+ * Update the topic's topic id
+ *
+ * @since 2.0.0 bbPress (r2954)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @return int Topic id
+ */
+function bbp_update_topic_topic_id( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$topic_id = bbp_update_topic_id( $topic_id, $topic_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_topic_id', $topic_id );
+}
+
+/**
+ * Adjust the total reply count of a topic
+ *
+ * @since 2.0.0 bbPress (r2467)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @param int $reply_count Optional. Set the reply count manually.
+ * @return int Topic reply count
+ */
+function bbp_update_topic_reply_count( $topic_id = 0, $reply_count = false ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Get replies of topic if not passed
+	$reply_count = ! is_int( $reply_count )
+		? bbp_get_public_child_count( $topic_id, bbp_get_reply_post_type() )
+		: (int) $reply_count;
+
+	update_post_meta( $topic_id, '_bbp_reply_count', $reply_count );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_reply_count', $reply_count, $topic_id );
+}
+
+/**
+ * Adjust the total hidden reply count of a topic (hidden includes trashed,
+ * spammed and pending replies)
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @param int $reply_count Optional. Set the reply count manually
+ * @return int Topic hidden reply count
+ */
+function bbp_update_topic_reply_count_hidden( $topic_id = 0, $reply_count = false ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Get replies of topic
+	$reply_count = ! is_int( $reply_count )
+		? bbp_get_non_public_child_count( $topic_id, bbp_get_reply_post_type() )
+		: (int) $reply_count;
+
+	update_post_meta( $topic_id, '_bbp_reply_count_hidden', $reply_count );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_reply_count_hidden', $reply_count, $topic_id );
+}
+
+/**
+ * Update the topic with the last active post ID
+ *
+ * @since 2.0.0 bbPress (r2888)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @param int $active_id Optional. active id
+ * @return int Active id
+ */
+function bbp_update_topic_last_active_id( $topic_id = 0, $active_id = 0 ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Get last public active id if not passed
+	if ( empty( $active_id ) ) {
+		$active_id = bbp_get_public_child_last_id( $topic_id, bbp_get_reply_post_type() );
+	}
+
+	// Adjust last_id's based on last_reply post_type
+	if ( empty( $active_id ) || ! bbp_is_reply( $active_id ) ) {
+		$active_id = $topic_id;
+	}
+
+	$active_id = (int) $active_id;
+
+	// Update only if published
+	update_post_meta( $topic_id, '_bbp_last_active_id', $active_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_last_active_id', $active_id, $topic_id );
+}
+
+/**
+ * Update the topics last active date/time (aka freshness)
+ *
+ * @since 2.0.0 bbPress (r2680)
+ *
+ * @param int    $topic_id Optional. Topic id.
+ * @param string $new_time Optional. New time in mysql format.
+ * @return string MySQL timestamp of last active reply
+ */
+function bbp_update_topic_last_active_time( $topic_id = 0, $new_time = '' ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Check time and use current if empty
+	if ( empty( $new_time ) ) {
+		$new_time = get_post_field( 'post_date', bbp_get_public_child_last_id( $topic_id, bbp_get_reply_post_type() ) );
+	}
+
+	// Update only if published
+	if ( ! empty( $new_time ) ) {
+		update_post_meta( $topic_id, '_bbp_last_active_time', $new_time );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_update_topic_last_active_time', $new_time, $topic_id );
+}
+
+/**
+ * Update the topic with the most recent reply ID
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @param int $reply_id Optional. Reply id
+ * @return int Reply id
+ */
+function bbp_update_topic_last_reply_id( $topic_id = 0, $reply_id = 0 ) {
+
+	// If it's a reply, then get the parent (topic id)
+	if ( empty( $reply_id ) && bbp_is_reply( $topic_id ) ) {
+		$reply_id = bbp_get_reply_id( $topic_id );
+		$topic_id = bbp_get_reply_topic_id( $reply_id );
+	} else {
+		$reply_id = bbp_get_reply_id( $reply_id );
+		$topic_id = bbp_get_topic_id( $topic_id );
+	}
+
+	if ( empty( $reply_id ) ) {
+		$reply_id = bbp_get_public_child_last_id( $topic_id, bbp_get_reply_post_type() );
+	}
+
+	// Adjust last_id's based on last_reply post_type
+	if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
+		$reply_id = 0;
+	}
+
+	$reply_id = (int) $reply_id;
+
+	// Update if reply is published
+	update_post_meta( $topic_id, '_bbp_last_reply_id', $reply_id );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_last_reply_id', $reply_id, $topic_id );
+}
+
+/**
+ * Adjust the total voice count of a topic
+ *
+ * @since 2.0.0 bbPress (r2567)
+ * @since 2.6.0 bbPress (r6515) This must be called after any engagement changes
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @return int Voice count
+ */
+function bbp_update_topic_voice_count( $topic_id = 0 ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Bail if no topic ID
+	if ( empty( $topic_id ) ) {
+		return;
+	}
+
+	// Count the engagements
+	$count = count( bbp_get_topic_engagements( $topic_id ) );
+
+	// Update the voice count for this topic id
+	update_post_meta( $topic_id, '_bbp_voice_count', $count );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_voice_count', $count, $topic_id );
+}
+
+/**
+ * Adjust the total anonymous reply count of a topic
+ *
+ * @since 2.0.0 bbPress (r2567)
+ *
+ * @param int $topic_id Optional. Topic id to update
+ * @return int Anonymous reply count
+ */
+function bbp_update_topic_anonymous_reply_count( $topic_id = 0 ) {
+
+	// If it's a reply, then get the parent (topic id)
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Query the DB to get anonymous replies in this topic
+	$bbp_db  = bbp_db();
+	$query   = $bbp_db->prepare( "SELECT COUNT( ID ) FROM {$bbp_db->posts} WHERE ( post_parent = %d AND post_status = %s AND post_type = %s AND post_author = 0 ) OR ( ID = %d AND post_type = %s AND post_author = 0 )", $topic_id, bbp_get_public_status_id(), bbp_get_reply_post_type(), $topic_id, bbp_get_topic_post_type() );
+	$replies = (int) $bbp_db->get_var( $query );
+
+	update_post_meta( $topic_id, '_bbp_anonymous_reply_count', $replies );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_update_topic_anonymous_reply_count', $replies, $topic_id );
+}
+
+/**
+ * Update the revision log of the topic
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param array $args Supports these args:
+ *  - topic_id: Topic id
+ *  - author_id: Author id
+ *  - reason: Reason for editing
+ *  - revision_id: Revision id
+ * @return mixed False on failure, true on success
+ */
+function bbp_update_topic_revision_log( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'reason'      => '',
+		'topic_id'    => 0,
+		'author_id'   => 0,
+		'revision_id' => 0
+	), 'update_topic_revision_log' );
+
+	// Populate the variables
+	$r['reason']      = bbp_format_revision_reason( $r['reason'] );
+	$r['topic_id']    = bbp_get_topic_id( $r['topic_id'] );
+	$r['author_id']   = bbp_get_user_id ( $r['author_id'], false, true );
+	$r['revision_id'] = (int) $r['revision_id'];
+
+	// Get the logs and append the new one to those
+	$revision_log                      = bbp_get_topic_raw_revision_log( $r['topic_id'] );
+	$revision_log[ $r['revision_id'] ] = array( 'author' => $r['author_id'], 'reason' => $r['reason'] );
+
+	// Finally, update
+	return update_post_meta( $r['topic_id'], '_bbp_revision_log', $revision_log );
+}
+
+/** Topic Actions *************************************************************/
+
+/**
+ * Closes a topic
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_close_topic( $topic_id = 0 ) {
+
+	// Get topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Get previous topic status meta
+	$status       = bbp_get_closed_status_id();
+	$topic_status = get_post_meta( $topic_id, '_bbp_status', true );
+
+	// Bail if already closed and topic status meta exists
+	if ( $status === $topic->post_status && ! empty( $topic_status ) ) {
+		return false;
+	}
+
+	// Set status meta public
+	$topic_status = $topic->post_status;
+
+	// Execute pre close code
+	do_action( 'bbp_close_topic', $topic_id );
+
+	// Add pre close status
+	add_post_meta( $topic_id, '_bbp_status', $topic_status );
+
+	// Set closed status
+	$topic->post_status = $status;
+
+	// Toggle revisions off as we are not altering content
+	if ( post_type_supports( bbp_get_topic_post_type(), 'revisions' ) ) {
+		$revisions_removed = true;
+		remove_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	// Update topic
+	$topic_id = wp_update_post( $topic );
+
+	// Toggle revisions back on
+	if ( true === $revisions_removed ) {
+		$revisions_removed = false;
+		add_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	// Execute post close code
+	do_action( 'bbp_closed_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Opens a topic
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_open_topic( $topic_id = 0 ) {
+
+	// Get topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Bail if not closed
+	if ( bbp_get_closed_status_id() !== $topic->post_status ) {
+		return false;
+	}
+
+	// Execute pre open code
+	do_action( 'bbp_open_topic', $topic_id );
+
+	// Get previous status
+	$topic_status = get_post_meta( $topic_id, '_bbp_status', true );
+
+	// If no previous status, default to publish
+	if ( empty( $topic_status ) ) {
+		$topic_status = bbp_get_public_status_id();
+	}
+
+	// Set previous status
+	$topic->post_status = $topic_status;
+
+	// Remove old status meta
+	delete_post_meta( $topic_id, '_bbp_status' );
+
+	// Toggle revisions off as we are not altering content
+	if ( post_type_supports( bbp_get_topic_post_type(), 'revisions' ) ) {
+		$revisions_removed = true;
+		remove_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	// Update topic
+	$topic_id = wp_update_post( $topic );
+
+	// Toggle revisions back on
+	if ( true === $revisions_removed ) {
+		$revisions_removed = false;
+		add_post_type_support( bbp_get_topic_post_type(), 'revisions' );
+	}
+
+	// Execute post open code
+	do_action( 'bbp_opened_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Marks a topic as spam
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_spam_topic( $topic_id = 0 ) {
+
+	// Get the topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Get new status
+	$status = bbp_get_spam_status_id();
+
+	// Bail if topic is spam
+	if ( $status === $topic->post_status ) {
+		return false;
+	}
+
+	// Add the original post status as post meta for future restoration
+	add_post_meta( $topic_id, '_bbp_spam_meta_status', $topic->post_status );
+
+	// Execute pre spam code
+	do_action( 'bbp_spam_topic', $topic_id );
+
+	// Set post status to spam
+	$topic->post_status = $status;
+
+	// Empty the topic of its tags
+	$topic->tax_input = bbp_spam_topic_tags( $topic_id );
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update the topic
+	$topic_id = wp_update_post( $topic );
+
+	// Execute post spam code
+	do_action( 'bbp_spammed_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Trash replies to a topic when it's marked as spam
+ *
+ * Usually you'll want to do this before the topic itself is marked as spam.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_spam_topic_replies( $topic_id = 0 ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Topic is being spammed, so its replies are trashed
+	$replies = new WP_Query( array(
+		'fields'         => 'id=>parent',
+		'post_type'      => bbp_get_reply_post_type(),
+		'post_status'    => bbp_get_public_status_id(),
+		'post_parent'    => $topic_id,
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	if ( ! empty( $replies->posts ) ) {
+
+		// Prevent debug notices
+		$pre_spammed_replies = array();
+
+		// Loop through replies, trash them, and add them to array
+		foreach ( $replies->posts as $reply ) {
+			wp_trash_post( $reply->ID );
+			$pre_spammed_replies[] = $reply->ID;
+		}
+
+		// Set a post_meta entry of the replies that were trashed by this action.
+		// This is so we can possibly untrash them, without untrashing replies
+		// that were purposefully trashed before.
+		update_post_meta( $topic_id, '_bbp_pre_spammed_replies', $pre_spammed_replies );
+
+		// Reset the global post data after looping through the above WP_Query
+		wp_reset_postdata();
+	}
+}
+
+/**
+ * Store the tags to a topic in post meta before it's marked as spam so they
+ * can be retrieved and unspammed later.
+ *
+ * Usually you'll want to do this before the topic itself is marked as spam.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_spam_topic_tags( $topic_id = 0 ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Get topic tags
+	$terms = get_the_terms( $topic_id, bbp_get_topic_tag_tax_id() );
+
+	// Define local variable(s)
+	$term_names = array();
+
+	// Topic has tags
+	if ( ! empty( $terms ) ) {
+
+		// Loop through and collect term names
+		foreach ( $terms as $term ) {
+			$term_names[] = $term->name;
+		}
+
+		// Topic terms have slugs
+		if ( ! empty( $term_names ) ) {
+
+			// Add the original post status as post meta for future restoration
+			add_post_meta( $topic_id, '_bbp_spam_topic_tags', $term_names );
+		}
+	}
+
+	return array( bbp_get_topic_tag_tax_id() => '' );
+}
+
+/**
+ * Unspams a topic
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_unspam_topic( $topic_id = 0 ) {
+
+	// Get the topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Bail if already not spam
+	if ( bbp_get_spam_status_id() !== $topic->post_status ) {
+		return false;
+	}
+
+	// Execute pre unspam code
+	do_action( 'bbp_unspam_topic', $topic_id );
+
+	// Get pre spam status
+	$topic_status = get_post_meta( $topic_id, '_bbp_spam_meta_status', true );
+
+	// If no previous status, default to publish
+	if ( empty( $topic_status ) ) {
+		$topic_status = bbp_get_public_status_id();
+	}
+
+	// Set post status to pre spam
+	$topic->post_status = $topic_status;
+	$topic->tax_input   = bbp_unspam_topic_tags( $topic_id );
+
+	// Delete pre spam meta
+	delete_post_meta( $topic_id, '_bbp_spam_meta_status' );
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update the topic
+	$topic_id = wp_update_post( $topic );
+
+	// Execute post unspam code
+	do_action( 'bbp_unspammed_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Untrash replies to a topic previously marked as spam.
+ *
+ * Usually you'll want to do this after the topic is unspammed.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_unspam_topic_replies( $topic_id = 0 ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Get the replies that were not previously trashed
+	$pre_spammed_replies = get_post_meta( $topic_id, '_bbp_pre_spammed_replies', true );
+
+	// There are replies to untrash
+	if ( ! empty( $pre_spammed_replies ) ) {
+
+		// Maybe reverse the trashed replies array
+		if ( is_array( $pre_spammed_replies ) ) {
+			$pre_spammed_replies = array_reverse( $pre_spammed_replies );
+		}
+
+		// Loop through replies
+		foreach ( (array) $pre_spammed_replies as $reply ) {
+			wp_untrash_post( $reply );
+		}
+	}
+
+	// Clear the trasheed reply meta for the topic
+	delete_post_meta( $topic_id, '_bbp_pre_spammed_replies' );
+}
+
+/**
+ * Retrieve tags to a topic from post meta before it's unmarked as spam so they.
+ *
+ * Usually you'll want to do this before the topic itself is unmarked as spam.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_unspam_topic_tags( $topic_id = 0 ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Get pre-spam topic tags
+	$terms = get_post_meta( $topic_id, '_bbp_spam_topic_tags', true );
+
+	// Delete pre-spam topic tag meta
+	if ( ! empty( $terms ) ) {
+		delete_post_meta( $topic_id, '_bbp_spam_topic_tags' );
+	}
+
+	return array( bbp_get_topic_tag_tax_id() => $terms );
+}
+
+/**
+ * Sticks a topic to a forum or front
+ *
+ * @since 2.0.0 bbPress (r2754)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param int $super Should we make the topic a super sticky?
+ * @return bool True on success, false on failure
+ */
+function bbp_stick_topic( $topic_id = 0, $super = false ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Bail if a topic is not a topic (prevents revisions as stickies)
+	if ( ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_stick_topic', $topic_id, $super );
+
+	// Maybe get the forum ID if not getting supers
+	$forum_id = empty( $super )
+		? bbp_get_topic_forum_id( $topic_id )
+		: 0;
+
+	// Get the stickies, maybe from the forum ID
+	$stickies = bbp_get_stickies( $forum_id );
+
+	// Add the topic to the stickies
+	$stickies[] = $topic_id;
+
+	// Pull out duplicates and empties
+	$stickies = array_unique( array_filter( $stickies ) );
+
+	// Unset incorrectly stuck revisions
+	foreach ( (array) $stickies as $key => $id ) {
+		if ( ! bbp_is_topic( $id ) ) {
+			unset( $stickies[ $key ] );
+		}
+	}
+
+	// Reset keys
+	$stickies = array_values( $stickies );
+
+	// Update
+	$success  = ! empty( $super )
+		? update_option( '_bbp_super_sticky_topics', $stickies )
+		: update_post_meta( $forum_id, '_bbp_sticky_topics', $stickies );
+
+	do_action( 'bbp_stuck_topic', $topic_id, $super, $success );
+
+	return (bool) $success;
+}
+
+/**
+ * Approves a pending topic
+ *
+ * @since 2.6.0 bbPress (r5503)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_approve_topic( $topic_id = 0 ) {
+
+	// Get topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Get new status
+	$status = bbp_get_public_status_id();
+
+	// Bail if already approved
+	if ( $status === $topic->post_status ) {
+		return false;
+	}
+
+	// Execute pre pending code
+	do_action( 'bbp_approve_topic', $topic_id );
+
+	// Set publish status
+	$topic->post_status = $status;
+
+	// Set post date GMT - prevents post_date override in wp_update_post()
+	$topic->post_date_gmt = get_gmt_from_date( $topic->post_date );
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update topic
+	$topic_id = wp_update_post( $topic );
+
+	// Execute post pending code
+	do_action( 'bbp_approved_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Unapproves a topic
+ *
+ * @since 2.6.0 bbPress (r5503)
+ *
+ * @param int $topic_id Topic id
+ * @return mixed False or {@link WP_Error} on failure, topic id on success
+ */
+function bbp_unapprove_topic( $topic_id = 0 ) {
+
+	// Get topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Get new status
+	$status = bbp_get_pending_status_id();
+
+	// Bail if already unapproved
+	if ( ! bbp_is_topic_public( $topic_id ) ) {
+		return false;
+	}
+
+	// Execute pre open code
+	do_action( 'bbp_unapprove_topic', $topic_id );
+
+	// Set pending status
+	$topic->post_status = $status;
+
+	// No revisions
+	remove_action( 'pre_post_update', 'wp_save_post_revision' );
+
+	// Update topic
+	$topic_id = wp_update_post( $topic );
+
+	// Execute post open code
+	do_action( 'bbp_unapproved_topic', $topic_id );
+
+	// Return topic_id
+	return $topic_id;
+}
+
+/**
+ * Unsticks a topic both from front and it's forum
+ *
+ * @since 2.0.0 bbPress (r2754)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool Always true.
+ */
+function bbp_unstick_topic( $topic_id = 0 ) {
+
+	// Get topic sticky status
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$super    = bbp_is_topic_super_sticky( $topic_id );
+	$forum_id = empty( $super ) ? bbp_get_topic_forum_id( $topic_id ) : 0;
+	$stickies = bbp_get_stickies( $forum_id );
+	$offset   = array_search( $topic_id, $stickies );
+
+	do_action( 'bbp_unstick_topic', $topic_id );
+
+	// Nothing to unstick
+	if ( empty( $stickies ) ) {
+		$success = true;
+
+	// Topic not in stickies
+	} elseif ( ! in_array( $topic_id, $stickies, true ) ) {
+		$success = true;
+
+	// Topic not in stickies
+	} elseif ( false === $offset ) {
+		$success = true;
+
+	// Splice out the offset
+	} else {
+		array_splice( $stickies, $offset, 1 );
+
+		if ( empty( $stickies ) ) {
+			$success = ! empty( $super )
+				? delete_option( '_bbp_super_sticky_topics' )
+				: delete_post_meta( $forum_id, '_bbp_sticky_topics' );
+		} else {
+			$success = ! empty( $super )
+				? update_option( '_bbp_super_sticky_topics', $stickies )
+				: update_post_meta( $forum_id, '_bbp_sticky_topics', $stickies );
+		}
+	}
+
+	do_action( 'bbp_unstuck_topic', $topic_id, $success );
+
+	return (bool) $success;
+}
+
+/** Before Delete/Trash/Untrash ***********************************************/
+
+/**
+ * Called before deleting a topic.
+ *
+ * This function is supplemental to the actual topic deletion which is
+ * handled by WordPress core API functions. It is used to clean up after
+ * a topic that is being deleted.
+ */
+function bbp_delete_topic( $topic_id = 0 ) {
+
+	// Validate topic ID
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_delete_topic', $topic_id );
+}
+
+/**
+ * Delete replies to a topic when it's deleted
+ *
+ * Usually you'll want to do this before the topic itself is deleted.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_delete_topic_replies( $topic_id = 0 ) {
+
+	// Validate topic ID
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Topic is being permanently deleted, so its replies gotta go too
+	// Note that we get all post statuses here
+	$replies = new WP_Query( array(
+		'fields'         => 'id=>parent',
+		'post_type'      => bbp_get_reply_post_type(),
+		'post_status'    => array_keys( get_post_stati() ),
+		'post_parent'    => $topic_id,
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	// Loop through and delete child replies
+	if ( ! empty( $replies->posts ) ) {
+		foreach ( $replies->posts as $reply ) {
+			wp_delete_post( $reply->ID, true );
+		}
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+}
+
+/**
+ * Called before trashing a topic
+ *
+ * This function is supplemental to the actual topic being trashed which is
+ * handled by WordPress core API functions. It is used to clean up after
+ * a topic that is being trashed.
+ */
+function bbp_trash_topic( $topic_id = 0 ) {
+
+	// Validate topic ID
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trash_topic', $topic_id );
+}
+
+/**
+ * Trash replies to a topic when it's trashed.
+ *
+ * Usually you'll want to do this before the topic itself is marked as spam.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_trash_topic_replies( $topic_id = 0 ) {
+
+	// Validate topic ID
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Topic is being trashed, so its replies are trashed too
+	$replies = new WP_Query( array(
+		'fields'         => 'id=>parent',
+		'post_type'      => bbp_get_reply_post_type(),
+		'post_status'    => bbp_get_public_status_id(),
+		'post_parent'    => $topic_id,
+		'posts_per_page' => -1,
+
+		// Performance
+		'nopaging'               => true,
+		'suppress_filters'       => true,
+		'update_post_term_cache' => false,
+		'update_post_meta_cache' => false,
+		'ignore_sticky_posts'    => true,
+		'no_found_rows'          => true
+	) );
+
+	if ( ! empty( $replies->posts ) ) {
+
+		// Prevent debug notices
+		$pre_trashed_replies = array();
+
+		// Loop through replies, trash them, and add them to array
+		foreach ( $replies->posts as $reply ) {
+			wp_trash_post( $reply->ID );
+			$pre_trashed_replies[] = $reply->ID;
+		}
+
+		// Set a post_meta entry of the replies that were trashed by this action.
+		// This is so we can possibly untrash them, without untrashing replies
+		// that were purposefully trashed before.
+		update_post_meta( $topic_id, '_bbp_pre_trashed_replies', $pre_trashed_replies );
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+}
+
+/**
+ * Called before untrashing a topic
+ */
+function bbp_untrash_topic( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrash_topic', $topic_id );
+}
+
+/**
+ * Untrash replies to a topic previously trashed.
+ *
+ * Usually you'll want to do this after the topic is unspammed.
+ *
+ * @since 2.6.0 bbPress (r5405)
+ *
+ * @param int $topic_id
+ */
+function bbp_untrash_topic_replies( $topic_id = 0 ) {
+
+	// Validation
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Get the replies that were not previously trashed
+	$pre_trashed_replies = get_post_meta( $topic_id, '_bbp_pre_trashed_replies', true );
+
+	// There are replies to untrash
+	if ( ! empty( $pre_trashed_replies ) ) {
+
+		// Maybe reverse the trashed replies array
+		if ( is_array( $pre_trashed_replies ) ) {
+			$pre_trashed_replies = array_reverse( $pre_trashed_replies );
+		}
+
+		// Loop through replies
+		foreach ( (array) $pre_trashed_replies as $reply ) {
+			wp_untrash_post( $reply );
+		}
+	}
+
+	// Clear the trashed reply meta for the topic
+	delete_post_meta( $topic_id, '_bbp_pre_trashed_replies' );
+}
+
+/** After Delete/Trash/Untrash ************************************************/
+
+/**
+ * Called after deleting a topic
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_deleted_topic( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_deleted_topic', $topic_id );
+}
+
+/**
+ * Called after trashing a topic
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_trashed_topic( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_trashed_topic', $topic_id );
+}
+
+/**
+ * Called after untrashing a topic
+ *
+ * @since 2.0.0 bbPress (r2993)
+ */
+function bbp_untrashed_topic( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	if ( empty( $topic_id ) || ! bbp_is_topic( $topic_id ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_untrashed_topic', $topic_id );
+}
+
+/** Settings ******************************************************************/
+
+/**
+ * Return the topics per page setting
+ *
+ * @since 2.0.0 bbPress (r3540)
+ * @return int
+ */
+function bbp_get_topics_per_page( $default = 15 ) {
+
+	// Get database option and cast as integer
+	$retval = get_option( '_bbp_topics_per_page', $default );
+
+	// If return val is empty, set it to default
+	if ( empty( $retval ) ) {
+		$retval = $default;
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_topics_per_page', $retval, $default );
+}
+
+/**
+ * Return the topics per RSS page setting
+ *
+ * @since 2.0.0 bbPress (r3540)
+ *
+ * @param int $default Default replies per page (25)
+ * @return int
+ */
+function bbp_get_topics_per_rss_page( $default = 25 ) {
+
+	// Get database option and cast as integer
+	$retval = get_option( '_bbp_topics_per_rss_page', $default );
+
+	// If return val is empty, set it to default
+	if ( empty( $retval ) ) {
+		$retval = $default;
+	}
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_topics_per_rss_page', $retval, $default );
+}
+
+/** Topic Tags ****************************************************************/
+
+/**
+ * Get topic tags for a specific topic ID
+ *
+ * @since 2.6.0 bbPress (r5836)
+ *
+ * @param int $topic_id
+ *
+ * @return string
+ */
+function bbp_get_topic_tags( $topic_id = 0 ) {
+	$topic_id   = bbp_get_topic_id( $topic_id );
+	$terms      = (array) get_the_terms( $topic_id, bbp_get_topic_tag_tax_id() );
+	$topic_tags = array_filter( $terms );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_tags', $topic_tags, $topic_id );
+}
+
+/**
+ * Get topic tags for a specific topic ID
+ *
+ * @since 2.2.0 bbPress (r4165)
+ *
+ * @param int    $topic_id
+ * @param string $sep
+ *
+ * @return string
+ */
+function bbp_get_topic_tag_names( $topic_id = 0, $sep = ', ' ) {
+	$topic_tags = bbp_get_topic_tags( $topic_id );
+	$pluck      = wp_list_pluck( $topic_tags, 'name' );
+	$terms      = ! empty( $pluck )
+		? implode( $sep, $pluck )
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_tag_names', $terms, $topic_id, $sep );
+}
+
+/**
+ * Will update topic-tag count based on object type.
+ *
+ * Function for the default callback for topic-tag taxonomies.
+ *
+ * @see https://bbpress.trac.wordpress.org/ticket/3043
+ * @access private
+ *
+ * @since 2.6.0 bbPress (r6253)
+ *
+ * @param array  $terms    List of Term taxonomy IDs.
+ * @param object $taxonomy Current taxonomy object of terms.
+ */
+function bbp_update_topic_tag_count( $terms, $taxonomy ) {
+
+	// Bail if no object types are available
+	if ( empty( $terms ) || empty( $taxonomy->object_type ) ) {
+		return;
+	}
+
+	// Get object types
+	$object_types = (array) $taxonomy->object_type;
+
+	foreach ( $object_types as &$object_type ) {
+		list( $object_type ) = explode( ':', $object_type );
+	}
+
+	$object_types = array_unique( $object_types );
+
+	if ( ! empty( $object_types ) ) {
+		$object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
+	}
+
+	// Statuses to count
+	$object_statuses = bbp_get_public_topic_statuses();
+
+	// Get database
+	$bbp_db = bbp_db();
+
+	// Loop through terms, maybe update counts
+	foreach ( (array) $terms as $term ) {
+		$count = 0;
+
+		// Get count, and bump it
+		if ( ! empty( $object_types ) ) {
+			$query    = "SELECT COUNT(*) FROM {$bbp_db->term_relationships}, {$bbp_db->posts} WHERE {$bbp_db->posts}.ID = {$bbp_db->term_relationships}.object_id AND post_status IN ('" . implode("', '", $object_statuses ) . "') AND post_type IN ('" . implode("', '", $object_types ) . "') AND term_taxonomy_id = %d";
+			$prepare  = $bbp_db->prepare( $query, $term );
+			$count   += (int) $bbp_db->get_var( $prepare );
+		}
+
+		/** This action is documented in wp-includes/taxonomy.php */
+		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
+		$bbp_db->update( $bbp_db->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
+
+		/** This action is documented in wp-includes/taxonomy.php */
+		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
+	}
+}
+
+/** Autoembed *****************************************************************/
+
+/**
+ * Check if autoembeds are enabled and hook them in if so
+ *
+ * @since 2.1.0 bbPress (r3752)
+ *
+ * @global WP_Embed $wp_embed
+ */
+function bbp_topic_content_autoembed() {
+	global $wp_embed;
+
+	if ( bbp_use_autoembed() && is_a( $wp_embed, 'WP_Embed' ) ) {
+		add_filter( 'bbp_get_topic_content', array( $wp_embed, 'autoembed' ), 2 );
+	}
+}
+
+/** Feeds *********************************************************************/
+
+/**
+ * Output an RSS2 feed of topics, based on the query passed.
+ *
+ * @since 2.0.0 bbPress (r3171)
+ *
+ * @param array $topics_query
+ */
+function bbp_display_topics_feed_rss2( $topics_query = array() ) {
+
+	// User cannot access this forum
+	if ( bbp_is_single_forum() && ! bbp_user_can_view_forum( array( 'forum_id' => bbp_get_forum_id() ) ) ) {
+		return;
+	}
+
+	// Feed title
+	$title = get_bloginfo_rss( 'name' ) . ' &#187; ' . esc_html__( 'All Topics', 'bbpress' );
+	$title = apply_filters( 'wp_title_rss', $title );
+
+	// Display the feed
+	header( 'Content-Type: ' . feed_content_type( 'rss2' ) . '; charset=' . get_option( 'blog_charset' ), true );
+	header( 'Status: 200 OK' );
+	echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . '>'; ?>
+
+	<rss version="2.0"
+		xmlns:content="http://purl.org/rss/1.0/modules/content/"
+		xmlns:wfw="http://wellformedweb.org/CommentAPI/"
+		xmlns:dc="http://purl.org/dc/elements/1.1/"
+		xmlns:atom="http://www.w3.org/2005/Atom"
+
+		<?php do_action( 'bbp_feed' ); ?>
+	>
+
+	<channel>
+
+		<title><?php echo $title; // Already escaped ?></title>
+		<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
+		<link><?php self_link(); ?></link>
+		<description><?php //?></description>
+		<lastBuildDate><?php echo date( 'r' ); ?></lastBuildDate>
+		<generator><?php echo esc_url_raw( 'https://bbpress.org/?v=' . convert_chars( bbp_get_version() ) ); ?></generator>
+		<language><?php bloginfo_rss( 'language' ); ?></language>
+
+		<?php do_action( 'bbp_feed_head' ); ?>
+
+		<?php if ( bbp_has_topics( $topics_query ) ) : ?>
+
+			<?php while ( bbp_topics() ) : bbp_the_topic(); ?>
+
+				<item>
+					<guid><?php bbp_topic_permalink(); ?></guid>
+					<title><![CDATA[<?php bbp_topic_title(); ?>]]></title>
+					<link><?php bbp_topic_permalink(); ?></link>
+					<pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_meta( bbp_get_topic_id(), '_bbp_last_active_time', true ), false ); ?></pubDate>
+					<dc:creator><?php the_author() ?></dc:creator>
+
+					<?php if ( !post_password_required() ) : ?>
+
+					<description>
+						<![CDATA[
+						<p><?php printf( esc_html__( 'Replies: %s', 'bbpress' ), bbp_get_topic_reply_count() ); ?></p>
+						<?php bbp_topic_content(); ?>
+						]]>
+					</description>
+
+					<?php rss_enclosure(); ?>
+
+					<?php endif; ?>
+
+					<?php do_action( 'bbp_feed_item' ); ?>
+
+				</item>
+
+				<?php endwhile; ?>
+			<?php endif; ?>
+
+		<?php do_action( 'bbp_feed_footer' ); ?>
+
+	</channel>
+	</rss>
+
+<?php
+	exit();
+}
+
+/** Permissions ***************************************************************/
+
+/**
+ * Redirect if unauthorized user is attempting to edit a topic
+ *
+ * @since 2.1.0 bbPress (r3605)
+ */
+function bbp_check_topic_edit() {
+
+	// Bail if not editing a topic
+	if ( ! bbp_is_topic_edit() ) {
+		return;
+	}
+
+	// User cannot edit topic, so redirect back to topic
+	if ( ! current_user_can( 'edit_topic', bbp_get_topic_id() ) ) {
+		bbp_redirect( bbp_get_topic_permalink() );
+	}
+}
+
+/**
+ * Redirect if unauthorized user is attempting to edit a topic tag
+ *
+ * @since 2.1.0 bbPress (r3605)
+ */
+function bbp_check_topic_tag_edit() {
+
+	// Bail if not editing a topic tag
+	if ( ! bbp_is_topic_tag_edit() ) {
+		return;
+	}
+
+	// Bail if current user cannot edit topic tags
+	if ( ! current_user_can( 'edit_topic_tag', bbp_get_topic_tag_id() ) ) {
+		bbp_redirect( bbp_get_topic_tag_link() );
+	}
+}
Index: /tags/2.6.6/src/includes/topics/index.php
===================================================================
--- /tags/2.6.6/src/includes/topics/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/topics/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/topics/template.php
===================================================================
--- /tags/2.6.6/src/includes/topics/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/topics/template.php	(revision 7162)
@@ -0,0 +1,4029 @@
+<?php
+
+/**
+ * bbPress Topic Template Tags
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Post Type *****************************************************************/
+
+/**
+ * Output the unique id of the custom post type for topics
+ *
+ * @since 2.0.0 bbPress (r2857)
+ */
+function bbp_topic_post_type() {
+	echo bbp_get_topic_post_type();
+}
+	/**
+	 * Return the unique id of the custom post type for topics
+	 *
+	 * @since 2.0.0 bbPress (r2857)
+	 *
+	 * @return string The unique topic post type id
+	 */
+	function bbp_get_topic_post_type() {
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_post_type', bbpress()->topic_post_type );
+	}
+
+/**
+ * Return array of labels used by the topic post type
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_topic_post_type_labels() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_post_type_labels', array(
+		'name'                     => esc_attr__( 'Topics',                     'bbpress' ),
+		'menu_name'                => esc_attr__( 'Topics',                     'bbpress' ),
+		'singular_name'            => esc_attr__( 'Topic',                      'bbpress' ),
+		'all_items'                => esc_attr__( 'All Topics',                 'bbpress' ),
+		'add_new'                  => esc_attr__( 'Add New',                    'bbpress' ),
+		'add_new_item'             => esc_attr__( 'Create New Topic',           'bbpress' ),
+		'edit'                     => esc_attr__( 'Edit',                       'bbpress' ),
+		'edit_item'                => esc_attr__( 'Edit Topic',                 'bbpress' ),
+		'new_item'                 => esc_attr__( 'New Topic',                  'bbpress' ),
+		'view'                     => esc_attr__( 'View Topic',                 'bbpress' ),
+		'view_item'                => esc_attr__( 'View Topic',                 'bbpress' ),
+		'view_items'               => esc_attr__( 'View Topics',                'bbpress' ),
+		'search_items'             => esc_attr__( 'Search Topics',              'bbpress' ),
+		'not_found'                => esc_attr__( 'No topics found',            'bbpress' ),
+		'not_found_in_trash'       => esc_attr__( 'No topics found in Trash',   'bbpress' ),
+		'filter_items_list'        => esc_attr__( 'Filter topics list',         'bbpress' ),
+		'items_list'               => esc_attr__( 'Topics list',                'bbpress' ),
+		'items_list_navigation'    => esc_attr__( 'Topics list navigation',     'bbpress' ),
+		'parent_item_colon'        => esc_attr__( 'Forum:',                     'bbpress' ),
+		'archives'                 => esc_attr__( 'Forum Topics',               'bbpress' ),
+		'attributes'               => esc_attr__( 'Topic Attributes',           'bbpress' ),
+		'insert_into_item'         => esc_attr__( 'Insert into topic',          'bbpress' ),
+		'uploaded_to_this_item'    => esc_attr__( 'Uploaded to this topic',     'bbpress' ),
+		'featured_image'           => esc_attr__( 'Topic Image',                'bbpress' ),
+		'set_featured_image'       => esc_attr__( 'Set topic image',            'bbpress' ),
+		'remove_featured_image'    => esc_attr__( 'Remove topic image',         'bbpress' ),
+		'use_featured_image'       => esc_attr__( 'Use as topic image',         'bbpress' ),
+		'item_published'           => esc_attr__( 'Topic published.',           'bbpress' ),
+		'item_published_privately' => esc_attr__( 'Topic published privately.', 'bbpress' ),
+		'item_reverted_to_draft'   => esc_attr__( 'Topic reverted to draft.',   'bbpress' ),
+		'item_scheduled'           => esc_attr__( 'Topic scheduled.',           'bbpress' ),
+		'item_updated'             => esc_attr__( 'Topic updated.',             'bbpress' )
+	) );
+}
+
+/**
+ * Return array of topic post type rewrite settings
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_topic_post_type_rewrite() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_post_type_rewrite', array(
+		'slug'       => bbp_get_topic_slug(),
+		'with_front' => false
+	) );
+}
+
+/**
+ * Return array of features the topic post type supports
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_topic_post_type_supports() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_post_type_supports', array(
+		'title',
+		'editor',
+		'revisions'
+	) );
+}
+
+/**
+ * The plugin version of bbPress comes with two topic display options:
+ * - Traditional: Topics are included in the reply loop (default)
+ * - New Style: Topics appear as "lead" posts, ahead of replies
+ *
+ * @since 2.0.0 bbPress (r2954)
+ *
+ * @param $show_lead Optional. Default false
+ * @return bool Yes if the topic appears as a lead, otherwise false
+ */
+function bbp_show_lead_topic( $show_lead = false ) {
+
+	// Never separate the lead topic in feeds
+	if ( is_feed() ) {
+		return false;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_show_lead_topic', (bool) $show_lead );
+}
+
+/** Topic Loop ****************************************************************/
+
+/**
+ * The main topic loop. WordPress makes this easy for us
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @param array $args All the arguments supported by {@link WP_Query}
+ * @return object Multidimensional array of topic information
+ */
+function bbp_has_topics( $args = array() ) {
+
+	/** Defaults **************************************************************/
+
+	// Other defaults
+	$default_topic_search  = bbp_sanitize_search_request( 'ts' );
+	$default_show_stickies = (bool) ( bbp_is_single_forum() || bbp_is_topic_archive() ) && ( false === $default_topic_search );
+	$default_post_parent   = bbp_is_single_forum() ? bbp_get_forum_id() : 'any';
+
+	// Default argument array
+	$default = array(
+		'post_type'      => bbp_get_topic_post_type(), // Narrow query down to bbPress topics
+		'post_parent'    => $default_post_parent,      // Forum ID
+		'meta_key'       => '_bbp_last_active_time',   // Make sure topic has some last activity time
+		'meta_type'      => 'DATETIME',
+		'orderby'        => 'meta_value',              // 'meta_value', 'author', 'date', 'title', 'modified', 'parent', rand',
+		'order'          => 'DESC',                    // 'ASC', 'DESC'
+		'posts_per_page' => bbp_get_topics_per_page(), // Topics per page
+		'paged'          => bbp_get_paged(),           // Page Number
+		'show_stickies'  => $default_show_stickies,    // Ignore sticky topics?
+		'max_num_pages'  => false,                     // Maximum number of pages to show
+
+		// Conditionally prime the cache for related posts
+		'update_post_family_cache' => true
+	);
+
+	// Only add 's' arg if searching for topics
+	// See https://bbpress.trac.wordpress.org/ticket/2607
+	if ( ! empty( $default_topic_search ) ) {
+		$default['s'] = $default_topic_search;
+	}
+
+	// What are the default allowed statuses (based on user caps)
+	if ( bbp_get_view_all( 'edit_others_topics' ) ) {
+
+		// Default view=all statuses
+		$post_statuses = array_keys( bbp_get_topic_statuses() );
+
+		// Add support for private status
+		if ( current_user_can( 'read_private_topics' ) ) {
+			$post_statuses[] = bbp_get_private_status_id();
+		}
+
+		// Join post statuses together
+		$default['post_status'] = $post_statuses;
+
+	// Lean on the 'perm' query var value of 'readable' to provide statuses
+	} else {
+		$default['perm'] = 'readable';
+	}
+
+	// Maybe query for topic tags
+	if ( bbp_is_topic_tag() ) {
+		$default['term']     = bbp_get_topic_tag_slug();
+		$default['taxonomy'] = bbp_get_topic_tag_tax_id();
+	}
+
+	/** Setup *****************************************************************/
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, $default, 'has_topics' );
+
+	// Get bbPress
+	$bbp = bbpress();
+
+	// Call the query
+	$bbp->topic_query = new WP_Query( $r );
+
+	// Maybe prime last active posts
+	if ( ! empty( $r['update_post_family_cache'] ) ) {
+		bbp_update_post_family_caches( $bbp->topic_query->posts );
+	}
+
+	// Set post_parent back to 0 if originally set to 'any'
+	if ( 'any' === $r['post_parent'] ) {
+		$r['post_parent'] = 0;
+	}
+
+	// Limited the number of pages shown
+	if ( ! empty( $r['max_num_pages'] ) ) {
+		$bbp->topic_query->max_num_pages = (int) $r['max_num_pages'];
+	}
+
+	/** Stickies **************************************************************/
+
+	// Put sticky posts at the top of the posts array
+	if ( ! empty( $r['show_stickies'] ) && ( $r['paged'] <= 1 ) ) {
+		bbp_add_sticky_topics( $bbp->topic_query, $r );
+	}
+
+	// If no limit to posts per page, set it to the current post_count
+	if ( -1 === $r['posts_per_page'] ) {
+		$r['posts_per_page'] = $bbp->topic_query->post_count;
+	}
+
+	// Add pagination values to query object
+	$bbp->topic_query->posts_per_page = (int) $r['posts_per_page'];
+	$bbp->topic_query->paged          = (int) $r['paged'];
+
+	// Only add pagination if query returned results
+	if ( ( ! empty( $bbp->topic_query->post_count ) || ! empty( $bbp->topic_query->found_posts ) ) && ! empty( $bbp->topic_query->posts_per_page ) ) {
+
+		// Limit the number of topics shown based on maximum allowed pages
+		if ( ( ! empty( $r['max_num_pages'] ) ) && ( $bbp->topic_query->found_posts > ( $bbp->topic_query->max_num_pages * $bbp->topic_query->post_count ) ) ) {
+			$bbp->topic_query->found_posts = $bbp->topic_query->max_num_pages * $bbp->topic_query->post_count;
+		}
+
+		// Total topics for pagination boundaries
+		$total_pages = ( $bbp->topic_query->posts_per_page === $bbp->topic_query->found_posts )
+			? 1
+			: ceil( $bbp->topic_query->found_posts / $bbp->topic_query->posts_per_page );
+
+		// Maybe add view-all args
+		$add_args = bbp_get_view_all()
+			? array( 'view' => 'all' )
+			: false;
+
+		// Pagination settings with filter
+		$bbp_topic_pagination = apply_filters( 'bbp_topic_pagination', array(
+			'base'      => bbp_get_topics_pagination_base( $r['post_parent'] ),
+			'format'    => '',
+			'total'     => $total_pages,
+			'current'   => $bbp->topic_query->paged,
+			'prev_text' => is_rtl() ? '&rarr;' : '&larr;',
+			'next_text' => is_rtl() ? '&larr;' : '&rarr;',
+			'mid_size'  => 1,
+			'add_args'  => $add_args,
+		) );
+
+		// Add pagination to query object
+		$bbp->topic_query->pagination_links = bbp_paginate_links( $bbp_topic_pagination );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_has_topics', $bbp->topic_query->have_posts(), $bbp->topic_query );
+}
+
+/**
+ * Whether there are more topics available in the loop
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @return object Topic information
+ */
+function bbp_topics() {
+
+	// Put into variable to check against next
+	$have_posts = bbpress()->topic_query->have_posts();
+
+	// Reset the post data when finished
+	if ( empty( $have_posts ) ) {
+		wp_reset_postdata();
+	}
+
+	return $have_posts;
+}
+
+/**
+ * Loads up the current topic in the loop
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @return object Topic information
+ */
+function bbp_the_topic() {
+	return bbpress()->topic_query->the_post();
+}
+
+/**
+ * Add sticky topics to a topics query object
+ *
+ * @since 2.6.0 bbPress (r6402)
+ *
+ * @param WP_Query $query
+ * @param array    $args
+ */
+function bbp_add_sticky_topics( &$query, $args = array() ) {
+
+	// Bail if intercepted
+	$intercept = bbp_maybe_intercept( __FUNCTION__, func_get_args() );
+	if ( bbp_is_intercepted( $intercept ) ) {
+		return $intercept;
+	}
+
+	// Parse arguments against what gets used locally
+	$r = bbp_parse_args( $args, array(
+		'post_parent'         => 0,
+		'post_parent__not_in' => array(),
+		'post__not_in'        => array(),
+		'post_status'         => '',
+		'perm'                => ''
+	), 'add_sticky_topics' );
+
+	// Get super stickies and stickies in this forum
+	$super_stickies = bbp_get_super_stickies();
+	$forum_stickies = ! empty( $r['post_parent'] )
+		? bbp_get_stickies( $r['post_parent'] )
+		: array();
+
+	// Merge stickies (supers first) and remove duplicates
+	$stickies = array_filter( array_unique( array_merge( $super_stickies, $forum_stickies ) ) );
+
+	// Bail if no stickies
+	if ( empty( $stickies ) ) {
+		return;
+	}
+
+	// If any posts have been excluded specifically, Ignore those that are sticky.
+	if ( ! empty( $r['post__not_in'] ) ) {
+		$stickies = array_diff( $stickies, $r['post__not_in'] );
+	}
+
+	// Default sticky posts array
+	$sticky_topics = array();
+
+	// Loop through posts
+	foreach ( $query->posts as $key => $post ) {
+
+		// Looking for stickies in this query loop, and stash & unset them
+		if ( in_array( $post->ID, $stickies, true ) ) {
+			$sticky_topics[] = $post;
+			unset( $query->posts[ $key ] );
+		}
+	}
+
+	// Remove queried stickies from stickies array
+	if ( ! empty( $sticky_topics ) ) {
+		$stickies = array_diff( $stickies, wp_list_pluck( $sticky_topics, 'ID' ) );
+	}
+
+	// Fetch all stickies that were not in the query
+	if ( ! empty( $stickies ) ) {
+
+		// Query to use in get_posts to get sticky posts
+		$sticky_query = array(
+			'post_type'   => bbp_get_topic_post_type(),
+			'post_parent' => 'any',
+			'meta_key'    => '_bbp_last_active_time',
+			'meta_type'   => 'DATETIME',
+			'orderby'     => 'meta_value',
+			'order'       => 'DESC',
+			'include'     => $stickies
+		);
+
+		// Conditionally exclude private/hidden forum ID's
+		$exclude_forum_ids = bbp_exclude_forum_ids( 'array' );
+
+		// Maybe remove the current forum from excluded forum IDs
+		if ( ! empty( $r['post_parent' ] ) ) {
+			unset( $exclude_forum_ids[ $r['post_parent' ] ] );
+		}
+
+		// Maybe exclude specific forums
+		if ( ! empty( $exclude_forum_ids ) ) {
+			$sticky_query['post_parent__not_in'] = $exclude_forum_ids;
+		}
+
+		// Allowed statuses, or lean on the 'perm' argument (probably 'readable')
+		$sticky_query['post_status'] = bbp_get_view_all( 'edit_others_topics' )
+			? $r['post_status']
+			: $r['perm'];
+
+		// Get unqueried stickies
+		$_posts = get_posts( $sticky_query );
+		if ( ! empty( $_posts ) ) {
+
+			// Merge the stickies topics with the query topics .
+			$sticky_topics = array_merge( $sticky_topics, $_posts );
+
+			// Get a count of the visible stickies
+			$sticky_count = count( $_posts );
+
+			// Adjust loop and counts for new sticky positions
+			$query->found_posts = (int) $query->found_posts + (int) $sticky_count;
+			$query->post_count  = (int) $query->post_count  + (int) $sticky_count;
+		}
+	}
+
+	// Bail if no sticky topics empty or not an array
+	if ( empty( $sticky_topics ) || ! is_array( $sticky_topics ) ) {
+		return;
+	}
+
+	// Default ordered stickies array
+	$ordered_stickies = array(
+		'supers' => array(),
+		'forums' => array()
+	);
+
+	// Separate supers from forums
+	foreach ( $sticky_topics as $post ) {
+		if ( in_array( $post->ID, $super_stickies, true ) ) {
+			$ordered_stickies['supers'][] = $post;
+		} elseif ( in_array( $post->ID, $forum_stickies, true ) ) {
+			$ordered_stickies['forums'][] = $post;
+		}
+	}
+
+	// Merge supers and forums, supers first
+	$sticky_topics = array_merge( $ordered_stickies['supers'], $ordered_stickies['forums'] );
+
+	// Update queried posts
+	$query->posts = array_merge( $sticky_topics, array_values( $query->posts ) );
+}
+
+/**
+ * Output the topic id
+ *
+ * @since 2.0.0 bbPress (r2485)
+ */
+function bbp_topic_id( $topic_id = 0) {
+	echo bbp_get_topic_id( $topic_id );
+}
+	/**
+	 * Return the topic id
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param $topic_id Optional. Used to check emptiness
+	 * @return int The topic id
+	 */
+	function bbp_get_topic_id( $topic_id = 0 ) {
+		$bbp      = bbpress();
+		$wp_query = bbp_get_wp_query();
+
+		// Easy empty checking
+		if ( ! empty( $topic_id ) && is_numeric( $topic_id ) ) {
+			$bbp_topic_id = $topic_id;
+
+		// Currently inside a topic loop
+		} elseif ( ! empty( $bbp->topic_query->in_the_loop ) && isset( $bbp->topic_query->post->ID ) ) {
+			$bbp_topic_id = $bbp->topic_query->post->ID;
+
+		// Currently inside a search loop
+		} elseif ( ! empty( $bbp->search_query->in_the_loop ) && isset( $bbp->search_query->post->ID ) && bbp_is_topic( $bbp->search_query->post->ID ) ) {
+			$bbp_topic_id = $bbp->search_query->post->ID;
+
+		// Currently viewing/editing a topic, likely alone
+		} elseif ( ( bbp_is_single_topic() || bbp_is_topic_edit() ) && ! empty( $bbp->current_topic_id ) ) {
+			$bbp_topic_id = $bbp->current_topic_id;
+
+		// Currently viewing/editing a topic, likely in a loop
+		} elseif ( ( bbp_is_single_topic() || bbp_is_topic_edit() ) && isset( $wp_query->post->ID ) ) {
+			$bbp_topic_id = $wp_query->post->ID;
+
+		// Currently viewing/editing a reply
+		} elseif ( bbp_is_single_reply() || bbp_is_reply_edit() ) {
+			$bbp_topic_id = bbp_get_reply_topic_id();
+
+		// Fallback
+		} else {
+			$bbp_topic_id = 0;
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_id', (int) $bbp_topic_id, $topic_id );
+	}
+
+/**
+ * Gets a topic
+ *
+ * @since 2.0.0 bbPress (r2787)
+ *
+ * @return mixed Null if error or topic (in specified form) if success
+ */
+function bbp_get_topic( $topic, $output = OBJECT, $filter = 'raw' ) {
+
+	// Maybe get ID from empty or int
+	if ( empty( $topic ) || is_numeric( $topic ) ) {
+		$topic = bbp_get_topic_id( $topic );
+	}
+
+	// Bail if no post object
+	$topic = get_post( $topic, OBJECT, $filter );
+	if ( empty( $topic ) ) {
+		return $topic;
+	}
+
+	// Bail if not correct post type
+	if ( $topic->post_type !== bbp_get_topic_post_type() ) {
+		return null;
+	}
+
+	// Default return value is OBJECT
+	$retval = $topic;
+
+	// Array A
+	if ( $output === ARRAY_A ) {
+		$retval = get_object_vars( $topic );
+
+	// Array N
+	} elseif ( $output === ARRAY_N ) {
+		$retval = array_values( get_object_vars( $topic ) );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic', $retval, $topic, $output, $filter );
+}
+
+/**
+ * Output the link to the topic in the topic loop
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param string $redirect_to Optional. Pass a redirect value for use with
+ *                              shortcodes and other fun things.
+ */
+function bbp_topic_permalink( $topic_id = 0, $redirect_to = '' ) {
+	echo esc_url( bbp_get_topic_permalink( $topic_id, $redirect_to ) );
+}
+	/**
+	 * Return the link to the topic
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param string $redirect_to Optional. Pass a redirect value for use with
+	 *                              shortcodes and other fun things.
+	 * @return string Permanent link to topic
+	 */
+	function bbp_get_topic_permalink( $topic_id = 0, $redirect_to = '' ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Use the redirect address
+		if ( ! empty( $redirect_to ) ) {
+			$topic_permalink = esc_url_raw( $redirect_to );
+
+		// Use the topic permalink
+		} else {
+			$topic_permalink = get_permalink( $topic_id );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_permalink', $topic_permalink, $topic_id );
+	}
+
+/**
+ * Output the title of the topic
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_title( $topic_id = 0 ) {
+	echo bbp_get_topic_title( $topic_id );
+}
+	/**
+	 * Return the title of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Title of topic
+	 */
+	function bbp_get_topic_title( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$title    = get_the_title( $topic_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_title', $title, $topic_id );
+	}
+
+/**
+ * Output the topic archive title
+ *
+ * @since 2.0.0 bbPress (r3249)
+ *
+ * @param string $title Default text to use as title
+ */
+function bbp_topic_archive_title( $title = '' ) {
+	echo bbp_get_topic_archive_title( $title );
+}
+	/**
+	 * Return the topic archive title
+	 *
+	 * @since 2.0.0 bbPress (r3249)
+	 *
+	 * @param string $title Default text to use as title
+	 *
+	 * @return string The topic archive title
+	 */
+	function bbp_get_topic_archive_title( $title = '' ) {
+
+		// If no title was passed
+		if ( empty( $title ) ) {
+
+			// Set root text to page title
+			$page = bbp_get_page_by_path( bbp_get_topic_archive_slug() );
+			if ( ! empty( $page ) ) {
+				$title = get_the_title( $page->ID );
+
+			// Default to topic post type name label
+			} else {
+				$tto    = get_post_type_object( bbp_get_topic_post_type() );
+				$title  = $tto->labels->name;
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_archive_title', $title );
+	}
+
+/**
+ * Output the content of the topic
+ *
+ * @since 2.0.0 bbPress (r2780)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_content( $topic_id = 0 ) {
+	echo bbp_get_topic_content( $topic_id );
+}
+	/**
+	 * Return the content of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2780)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Content of the topic
+	 */
+	function bbp_get_topic_content( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Check if password is required
+		if ( post_password_required( $topic_id ) ) {
+			return get_the_password_form();
+		}
+
+		$content = get_post_field( 'post_content', $topic_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_content', $content, $topic_id );
+	}
+
+/**
+ * Output the excerpt of the topic
+ *
+ * @since 2.0.0 bbPress (r2780)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param int $length Optional. Length of the excerpt. Defaults to 100 letters
+ */
+function bbp_topic_excerpt( $topic_id = 0, $length = 100 ) {
+	echo bbp_get_topic_excerpt( $topic_id, $length );
+}
+	/**
+	 * Return the excerpt of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2780)
+	 *
+	 * @param int $topic_id Optional. topic id
+	 * @param int $length Optional. Length of the excerpt. Defaults to 100
+	 *                     letters
+	 * @return string topic Excerpt
+	 */
+	function bbp_get_topic_excerpt( $topic_id = 0, $length = 100 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$length   = (int) $length;
+		$excerpt  = get_post_field( 'post_excerpt', $topic_id );
+
+		if ( empty( $excerpt ) ) {
+			$excerpt = bbp_get_topic_content( $topic_id );
+		}
+
+		$excerpt = trim( strip_tags( $excerpt ) );
+
+		// Multibyte support
+		if ( function_exists( 'mb_strlen' ) ) {
+			$excerpt_length = mb_strlen( $excerpt );
+		} else {
+			$excerpt_length = strlen( $excerpt );
+		}
+
+		if ( ! empty( $length ) && ( $excerpt_length > $length ) ) {
+			$excerpt  = mb_substr( $excerpt, 0, $length - 1 );
+			$excerpt .= '&hellip;';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_excerpt', $excerpt, $topic_id, $length );
+	}
+
+/**
+ * Output the post date and time of a topic
+ *
+ * @since 2.2.0 bbPress (r4155)
+ *
+ * @param int $topic_id Optional. Topic id.
+ * @param bool $humanize Optional. Humanize output using time_since
+ * @param bool $gmt Optional. Use GMT
+ */
+function bbp_topic_post_date( $topic_id = 0, $humanize = false, $gmt = false ) {
+	echo bbp_get_topic_post_date( $topic_id, $humanize, $gmt );
+}
+	/**
+	 * Return the post date and time of a topic
+	 *
+	 * @since 2.2.0 bbPress (r4155)
+	 *
+	 * @param int $topic_id Optional. Topic id.
+	 * @param bool $humanize Optional. Humanize output using time_since
+	 * @param bool $gmt Optional. Use GMT
+	 * @return string
+	 */
+	function bbp_get_topic_post_date( $topic_id = 0, $humanize = false, $gmt = false ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// 4 days, 4 hours ago
+		if ( ! empty( $humanize ) ) {
+			$gmt_s  = ! empty( $gmt ) ? 'G' : 'U';
+			$date   = get_post_time( $gmt_s, $gmt, $topic_id );
+			$time   = false; // For filter below
+			$result = bbp_get_time_since( $date );
+
+		// August 4, 2012 at 2:37 pm
+		} else {
+			$date   = get_post_time( get_option( 'date_format' ), $gmt, $topic_id, true );
+			$time   = get_post_time( get_option( 'time_format' ), $gmt, $topic_id, true );
+			$result = sprintf( _x( '%1$s at %2$s', 'date at time', 'bbpress' ), $date, $time );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_post_date', $result, $topic_id, $humanize, $gmt, $date, $time );
+	}
+
+/**
+ * Output pagination links of a topic within the topic loop
+ *
+ * @since 2.0.0 bbPress (r2966)
+ *
+ * @param array $args See {@link bbp_get_topic_pagination()}
+ */
+function bbp_topic_pagination( $args = array() ) {
+	echo bbp_get_topic_pagination( $args );
+}
+	/**
+	 * Returns pagination links of a topic within the topic loop
+	 *
+	 * @since 2.0.0 bbPress (r2966)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - topic_id: Topic id
+	 *  - before: Before the links
+	 *  - after: After the links
+	 * @return string Pagination links
+	 */
+	function bbp_get_topic_pagination( $args = array() ) {
+
+		// Bail if threading replies
+		if ( bbp_thread_replies() ) {
+			return;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'topic_id' => bbp_get_topic_id(),
+			'before'   => '<span class="bbp-topic-pagination">',
+			'after'    => '</span>',
+		), 'get_topic_pagination' );
+
+		// Slug must be checked for topics that have never been approved/published
+		$has_slug = bbp_get_topic( $r['topic_id'] )->post_name;
+
+		// If pretty permalinks are enabled, make our pagination pretty
+		$base = ! empty( $has_slug ) && bbp_use_pretty_urls() && ! bbp_is_topic_pending( $r['topic_id'] )
+			? trailingslashit( get_permalink( $r['topic_id'] ) ) . user_trailingslashit( bbp_get_paged_slug() . '/%#%/' )
+			: add_query_arg( 'paged', '%#%', get_permalink( $r['topic_id'] ) );
+
+		// Get total and add 1 if topic is included in the reply loop
+		$total = bbp_get_topic_reply_count( $r['topic_id'], true );
+
+		// Bump if topic is in loop
+		if ( ! bbp_show_lead_topic() ) {
+			$total++;
+		}
+
+		// Total for pagination boundaries
+		$total_pages = ceil( $total / bbp_get_replies_per_page() );
+
+		// Maybe add view-all args
+		$add_args = bbp_get_view_all( 'edit_others_replies' )
+			? array( 'view' => 'all' )
+			: false;
+
+		// Pagination settings with filter
+		$bbp_topic_pagination = apply_filters( 'bbp_get_topic_pagination', array(
+			'base'      => $base,
+			'total'     => $total_pages,
+			'current'   => 0,
+			'prev_next' => false,
+			'mid_size'  => 2,
+			'end_size'  => 2,
+			'add_args'  => $add_args
+		) );
+
+		// Add pagination to query object
+		$pagination_links = bbp_paginate_links( $bbp_topic_pagination );
+
+		// Maybe add before and after to pagination links
+		if ( ! empty( $pagination_links ) ) {
+			$pagination_links = $r['before'] . $pagination_links . $r['after'];
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_pagination', $pagination_links, $args );
+	}
+
+/**
+ * Append revisions to the topic content
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param string $content Optional. Content to which we need to append the revisions to
+ * @param int $topic_id Optional. Topic id
+ * @return string Content with the revisions appended
+ */
+function bbp_topic_content_append_revisions( $content = '', $topic_id = 0 ) {
+
+	// Bail if in admin or feed
+	if ( is_admin() || is_feed() ) {
+		return;
+	}
+
+	// Validate the ID
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Filter & return
+	return apply_filters( 'bbp_topic_append_revisions', $content . bbp_get_topic_revision_log( $topic_id ), $content, $topic_id );
+}
+
+/**
+ * Output the revision log of the topic
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_revision_log( $topic_id = 0 ) {
+	echo bbp_get_topic_revision_log( $topic_id );
+}
+	/**
+	 * Return the formatted revision log of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2782)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Revision log of the topic
+	 */
+	function bbp_get_topic_revision_log( $topic_id = 0 ) {
+
+		// Create necessary variables
+		$topic_id     = bbp_get_topic_id( $topic_id );
+		$revision_log = bbp_get_topic_raw_revision_log( $topic_id );
+
+		if ( empty( $topic_id ) || empty( $revision_log ) || ! is_array( $revision_log ) ) {
+			return false;
+		}
+
+		$revisions = bbp_get_topic_revisions( $topic_id );
+		if ( empty( $revisions ) ) {
+			return false;
+		}
+
+		$retval = "\n\n" . '<ul id="bbp-topic-revision-log-' . esc_attr( $topic_id ) . '" class="bbp-topic-revision-log">' . "\n\n";
+
+		// Loop through revisions
+		foreach ( (array) $revisions as $revision ) {
+
+			if ( empty( $revision_log[ $revision->ID ] ) ) {
+				$author_id = $revision->post_author;
+				$reason    = '';
+			} else {
+				$author_id = $revision_log[ $revision->ID ]['author'];
+				$reason    = $revision_log[ $revision->ID ]['reason'];
+			}
+
+			$author = bbp_get_author_link( array( 'size' => 14, 'link_text' => bbp_get_topic_author_display_name( $revision->ID ), 'post_id' => $revision->ID ) );
+			$since  = bbp_get_time_since( bbp_convert_date( $revision->post_modified ) );
+
+			$retval .= "\t" . '<li id="bbp-topic-revision-log-' . esc_attr( $topic_id ) . '-item-' . esc_attr( $revision->ID ) . '" class="bbp-topic-revision-log-item">' . "\n";
+			if ( ! empty( $reason ) ) {
+				$retval .= "\t\t" . sprintf( esc_html__( 'This topic was modified %1$s by %2$s. Reason: %3$s', 'bbpress' ), esc_html( $since ), $author, esc_html( $reason ) ) . "\n";
+			} else {
+				$retval .= "\t\t" . sprintf( esc_html__( 'This topic was modified %1$s by %2$s.',              'bbpress' ), esc_html( $since ), $author ) . "\n";
+			}
+			$retval .= "\t" . '</li>' . "\n";
+		}
+
+		$retval .= "\n" . '</ul>' . "\n\n";
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_revision_log', $retval, $topic_id );
+	}
+		/**
+		 * Return the raw revision log of the topic
+		 *
+		 * @since 2.0.0 bbPress (r2782)
+		 *
+		 * @param int $topic_id Optional. Topic id
+		 * @return string Raw revision log of the topic
+		 */
+		function bbp_get_topic_raw_revision_log( $topic_id = 0 ) {
+			$topic_id = bbp_get_topic_id( $topic_id );
+
+			$revision_log = get_post_meta( $topic_id, '_bbp_revision_log', true );
+			$revision_log = ! empty( $revision_log )
+				? $revision_log
+				: array();
+
+			// Filter & return
+			return apply_filters( 'bbp_get_topic_raw_revision_log', $revision_log, $topic_id );
+		}
+
+/**
+ * Return the revisions of the topic
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return string Topic revisions
+ */
+function bbp_get_topic_revisions( $topic_id = 0 ) {
+	$topic_id  = bbp_get_topic_id( $topic_id );
+	$revisions = wp_get_post_revisions( $topic_id, array( 'order' => 'ASC' ) );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topic_revisions', $revisions, $topic_id );
+}
+
+/**
+ * Return the revision count of the topic
+ *
+ * @since 2.0.0 bbPress (r2782)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return string Topic revision count
+ */
+function bbp_get_topic_revision_count( $topic_id = 0, $integer = false ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$count    = count( bbp_get_topic_revisions( $topic_id ) );
+	$filter   = ( true === $integer )
+		? 'bbp_get_topic_revision_count_int'
+		: 'bbp_get_topic_revision_count';
+
+	return apply_filters( $filter, $count, $topic_id );
+}
+
+/**
+ * Is the topic a sticky or super sticky?
+ *
+ * @since 2.0.0 bbPress (r2754)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param int $check_super Optional. If set to true and if the topic is not a
+ *                           normal sticky, it is checked if it is a super
+ *                           sticky or not. Defaults to true.
+ * @return bool True if sticky or super sticky, false if not.
+ */
+function bbp_is_topic_sticky( $topic_id = 0, $check_super = true ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$forum_id = bbp_get_topic_forum_id( $topic_id );
+	$stickies = bbp_get_stickies( $forum_id );
+	$retval   = in_array( $topic_id, $stickies, true );
+
+	// Maybe check super stickies
+	if ( ( false === $retval ) && ( true === $check_super ) ) {
+		$retval = bbp_is_topic_super_sticky( $topic_id );
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_sticky', $retval, $topic_id, $check_super );
+}
+
+/**
+ * Is the topic a super sticky?
+ *
+ * @since 2.0.0 bbPress (r2754)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if super sticky, false if not.
+ */
+function bbp_is_topic_super_sticky( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$stickies = bbp_get_super_stickies( $topic_id );
+	$retval   = in_array( $topic_id, $stickies, true );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_super_sticky', $retval, $topic_id );
+}
+
+/**
+ * Output the status of the topic
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_status( $topic_id = 0 ) {
+	echo bbp_get_topic_status( $topic_id );
+}
+	/**
+	 * Return the status of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Status of topic
+	 */
+	function bbp_get_topic_status( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_status', get_post_status( $topic_id ), $topic_id );
+	}
+
+/**
+ * Is the topic closed to new replies?
+ *
+ * @since 2.0.0 bbPress (r2746)
+ *
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if closed, false if not.
+ */
+function bbp_is_topic_closed( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_closed_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_closed', $topic_status, $topic_id );
+}
+
+/**
+ * Is the topic open to new replies?
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if open, false if closed.
+ */
+function bbp_is_topic_open( $topic_id = 0 ) {
+	return ! bbp_is_topic_closed( $topic_id );
+}
+
+/**
+ * Is the topic publicly viewable?
+ *
+ * See bbp_get_public_topic_statuses() for public statuses.
+ *
+ * @since 2.6.0 bbPress (r6383)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if public, false if not.
+ */
+function bbp_is_topic_public( $topic_id = 0 ) {
+	$topic_id  = bbp_get_topic_id( $topic_id );
+	$status    = bbp_get_topic_status( $topic_id );
+	$public    = bbp_get_public_topic_statuses();
+	$is_public = in_array( $status, $public, true );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_public', $is_public, $topic_id );
+}
+
+/**
+ * Does the topic have a published status?
+ *
+ * @since 2.0.0 bbPress (r3496)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if published, false if not.
+ */
+function bbp_is_topic_published( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_public_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_published', $topic_status, $topic_id );
+}
+
+/**
+ * Is the topic marked as spam?
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if spam, false if not.
+ */
+function bbp_is_topic_spam( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_spam_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_spam', $topic_status, $topic_id );
+}
+
+/**
+ * Is the topic trashed?
+ *
+ * @since 2.0.0 bbPress (r2888)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if trashed, false if not.
+ */
+function bbp_is_topic_trash( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_trash_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_trash', $topic_status, $topic_id );
+}
+
+/**
+ * Is the topic pending?
+ *
+ * @since 2.6.0 bbPress (r5504)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if pending, false if not.
+ */
+function bbp_is_topic_pending( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_pending_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_pending', $topic_status, $topic_id );
+}
+
+/**
+ * Is the topic private?
+ *
+ * @since 2.6.0 bbPress (r5504)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if private, false if not.
+ */
+function bbp_is_topic_private( $topic_id = 0 ) {
+	$topic_id     = bbp_get_topic_id( $topic_id );
+	$status       = bbp_get_private_status_id();
+	$topic_status = ( bbp_get_topic_status( $topic_id ) === $status );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_private', $topic_status, $topic_id );
+}
+
+/**
+ * Is the posted by an anonymous user?
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if the post is by an anonymous user, false if not.
+ */
+function bbp_is_topic_anonymous( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$retval   = false;
+
+	if ( ! bbp_get_topic_author_id( $topic_id ) ) {
+		$retval = true;
+
+	} elseif ( get_post_meta( $topic_id, '_bbp_anonymous_name',  true ) ) {
+		$retval = true;
+
+	} elseif ( get_post_meta( $topic_id, '_bbp_anonymous_email', true ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_topic_anonymous', $retval, $topic_id );
+}
+
+/**
+ * Deprecated. Use bbp_topic_author_display_name() instead.
+ *
+ * Output the author of the topic.
+ *
+ * @since 2.0.0 bbPress (r2590)
+ *
+ * @deprecated 2.5.0 bbPress (r5119)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_author( $topic_id = 0 ) {
+	echo bbp_get_topic_author( $topic_id );
+}
+	/**
+	 * Deprecated. Use bbp_get_topic_author_display_name() instead.
+	 *
+	 * Return the author of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2590)
+	 *
+	 * @deprecated 2.5.0 bbPress (r5119)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Author of topic
+	 */
+	function bbp_get_topic_author( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		if ( ! bbp_is_topic_anonymous( $topic_id ) ) {
+			$author = get_the_author_meta( 'display_name', bbp_get_topic_author_id( $topic_id ) );
+		} else {
+			$author = get_post_meta( $topic_id, '_bbp_anonymous_name', true );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author', $author, $topic_id );
+	}
+
+/**
+ * Output the author ID of the topic
+ *
+ * @since 2.0.0 bbPress (r2590)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_author_id( $topic_id = 0 ) {
+	echo bbp_get_topic_author_id( $topic_id );
+}
+	/**
+	 * Return the author ID of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2590)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Author of topic
+	 */
+	function bbp_get_topic_author_id( $topic_id = 0 ) {
+		$topic_id  = bbp_get_topic_id( $topic_id );
+		$author_id = get_post_field( 'post_author', $topic_id );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_author_id', (int) $author_id, $topic_id );
+	}
+
+/**
+ * Output the author display_name of the topic
+ *
+ * @since 2.0.0 bbPress (r2590)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_author_display_name( $topic_id = 0 ) {
+	echo bbp_get_topic_author_display_name( $topic_id );
+}
+	/**
+	 * Return the author display_name of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic's author's display name
+	 */
+	function bbp_get_topic_author_display_name( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Check for anonymous user
+		if ( ! bbp_is_topic_anonymous( $topic_id ) ) {
+
+			// Get the author ID
+			$author_id = bbp_get_topic_author_id( $topic_id );
+
+			// Try to get a display name
+			$author_name = get_the_author_meta( 'display_name', $author_id );
+
+			// Fall back to user login
+			if ( empty( $author_name ) ) {
+				$author_name = get_the_author_meta( 'user_login', $author_id );
+			}
+
+		// User does not have an account
+		} else {
+			$author_name = get_post_meta( $topic_id, '_bbp_anonymous_name', true );
+		}
+
+		// Fallback if nothing could be found
+		if ( empty( $author_name ) ) {
+			$author_name = bbp_get_fallback_display_name( $topic_id );
+		}
+
+		// Encode possible UTF8 display names
+		if ( seems_utf8( $author_name ) === false ) {
+			$author_name = utf8_encode( $author_name );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_display_name', $author_name, $topic_id );
+	}
+
+/**
+ * Output the author avatar of the topic
+ *
+ * @since 2.0.0 bbPress (r2590)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param int $size Optional. Avatar size. Defaults to 40
+ */
+function bbp_topic_author_avatar( $topic_id = 0, $size = 40 ) {
+	echo bbp_get_topic_author_avatar( $topic_id, $size );
+}
+	/**
+	 * Return the author avatar of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2590)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param int $size Optional. Avatar size. Defaults to 40
+	 * @return string Avatar of the author of the topic
+	 */
+	function bbp_get_topic_author_avatar( $topic_id = 0, $size = 40 ) {
+		$author_avatar = '';
+
+		$topic_id = bbp_get_topic_id( $topic_id );
+		if ( ! empty( $topic_id ) ) {
+			if ( ! bbp_is_topic_anonymous( $topic_id ) ) {
+				$author_avatar = get_avatar( bbp_get_topic_author_id( $topic_id ), $size );
+			} else {
+				$author_avatar = get_avatar( get_post_meta( $topic_id, '_bbp_anonymous_email', true ), $size );
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_avatar', $author_avatar, $topic_id, $size );
+	}
+
+/**
+ * Output the author link of the topic
+ *
+ * @since 2.0.0 bbPress (r2717)
+ *
+ * @param mixed|int $args If it is an integer, it is used as topic_id. Optional.
+ */
+function bbp_topic_author_link( $args = array() ) {
+	echo bbp_get_topic_author_link( $args );
+}
+	/**
+	 * Return the author link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2717)
+	 *
+	 * @param mixed|int $args If it is an integer, it is used as topic id.
+	 *                         Optional.
+	 * @return string Author link of topic
+	 */
+	function bbp_get_topic_author_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'post_id'    => 0,
+			'link_title' => '',
+			'type'       => 'both',
+			'size'       => 80,
+			'sep'        => '',
+			'show_role'  => false
+		), 'get_topic_author_link' );
+
+		// Default return value
+		$author_link = '';
+
+		// Used as topic_id
+		$topic_id = is_numeric( $args )
+			? bbp_get_topic_id( $args )
+			: bbp_get_topic_id( $r['post_id'] );
+
+		// Topic ID is good
+		if ( ! empty( $topic_id ) ) {
+
+			// Get some useful topic information
+			$author_url = bbp_get_topic_author_url( $topic_id );
+			$anonymous  = bbp_is_topic_anonymous( $topic_id );
+
+			// Tweak link title if empty
+			if ( empty( $r['link_title'] ) ) {
+				$author = bbp_get_topic_author_display_name( $topic_id );
+				$title  = empty( $anonymous )
+					? esc_attr__( "View %s's profile",  'bbpress' )
+					: esc_attr__( "Visit %s's website", 'bbpress' );
+
+				$link_title = sprintf( $title, $author );
+
+			// Use what was passed if not
+			} else {
+				$link_title = $r['link_title'];
+			}
+
+			// Setup title and author_links array
+			$author_links = array();
+			$link_title   = ! empty( $link_title )
+				? ' title="' . esc_attr( $link_title ) . '"'
+				: '';
+
+			// Get avatar (unescaped, because HTML)
+			if ( ( 'avatar' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['avatar'] = bbp_get_topic_author_avatar( $topic_id, $r['size'] );
+			}
+
+			// Get display name (escaped, because never HTML)
+			if ( ( 'name' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['name'] = esc_html( bbp_get_topic_author_display_name( $topic_id ) );
+			}
+
+			// Empty array
+			$links  = array();
+			$sprint = '<span %1$s>%2$s</span>';
+
+			// Wrap each link
+			foreach ( $author_links as $link => $link_text ) {
+				$link_class = ' class="bbp-author-' . esc_attr( $link ) . '"';
+				$links[]    = sprintf( $sprint, $link_class, $link_text );
+			}
+
+			// Juggle
+			$author_links = $links;
+			unset( $links );
+
+			// Filter sections
+			$sections    = apply_filters( 'bbp_get_topic_author_links', $author_links, $r, $args );
+
+			// Assemble sections into author link
+			$author_link = implode( $r['sep'], $sections );
+
+			// Only wrap in link if profile exists
+			if ( empty( $anonymous ) && bbp_user_has_profile( bbp_get_topic_author_id( $topic_id ) ) ) {
+				$author_link = sprintf( '<a href="%1$s"%2$s%3$s>%4$s</a>', esc_url( $author_url ), $link_title, ' class="bbp-author-link"', $author_link );
+			}
+
+			// Role is not linked
+			if ( true === $r['show_role'] ) {
+				$author_link .= bbp_get_topic_author_role( array( 'topic_id' => $topic_id ) );
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_link', $author_link, $r, $args );
+	}
+
+/**
+ * Output the author url of the topic
+ *
+ * @since 2.0.0 bbPress (r2590)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_author_url( $topic_id = 0 ) {
+	echo esc_url( bbp_get_topic_author_url( $topic_id ) );
+}
+
+	/**
+	 * Return the author url of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2590)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Author URL of topic
+	 */
+	function bbp_get_topic_author_url( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Check for anonymous user or non-existant user
+		if ( ! bbp_is_topic_anonymous( $topic_id ) && bbp_user_has_profile( bbp_get_topic_author_id( $topic_id ) ) ) {
+			$author_url = bbp_get_user_profile_url( bbp_get_topic_author_id( $topic_id ) );
+		} else {
+			$author_url = get_post_meta( $topic_id, '_bbp_anonymous_website', true );
+
+			// Set empty author_url as empty string
+			if ( empty( $author_url ) ) {
+				$author_url = '';
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_url', $author_url, $topic_id );
+	}
+
+/**
+ * Output the topic author email address
+ *
+ * @since 2.0.0 bbPress (r3445)
+ *
+ * @param int $topic_id Optional. Reply id
+ */
+function bbp_topic_author_email( $topic_id = 0 ) {
+	echo bbp_get_topic_author_email( $topic_id );
+}
+	/**
+	 * Return the topic author email address
+	 *
+	 * @since 2.0.0 bbPress (r3445)
+	 *
+	 * @param int $topic_id Optional. Reply id
+	 * @return string Topic author email address
+	 */
+	function bbp_get_topic_author_email( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Not anonymous user
+		if ( ! bbp_is_topic_anonymous( $topic_id ) ) {
+
+			// Use topic author email address
+			$user_id      = bbp_get_topic_author_id( $topic_id );
+			$user         = get_userdata( $user_id );
+			$author_email = ! empty( $user->user_email ) ? $user->user_email : '';
+
+		// Anonymous
+		} else {
+
+			// Get email from post meta
+			$author_email = get_post_meta( $topic_id, '_bbp_anonymous_email', true );
+
+			// Sanity check for missing email address
+			if ( empty( $author_email ) ) {
+				$author_email = '';
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_email', $author_email, $topic_id );
+	}
+
+/**
+ * Output the topic author role
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @param array $args Optional.
+ */
+function bbp_topic_author_role( $args = array() ) {
+	echo bbp_get_topic_author_role( $args );
+}
+	/**
+	 * Return the topic author role
+	 *
+	 * @since 2.1.0 bbPress (r3860)
+	 *
+	 * @param array $args Optional.
+	 * @return string topic author role
+	 */
+	function bbp_get_topic_author_role( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'topic_id' => 0,
+			'class'    => false,
+			'before'   => '<div class="bbp-author-role">',
+			'after'    => '</div>'
+		), 'get_topic_author_role' );
+
+		$topic_id = bbp_get_topic_id( $r['topic_id'] );
+		$role     = bbp_get_user_display_role( bbp_get_topic_author_id( $topic_id ) );
+
+		// Backwards compatibilty with old 'class' argument
+		if ( ! empty( $r['class'] ) ) {
+			$author_role = sprintf( '%1$s<div class="%2$s">%3$s</div>%4$s', $r['before'], $r['class'], $role, $r['after'] );
+
+		// Simpler before & after arguments
+		// https://bbpress.trac.wordpress.org/ticket/2557
+		} else {
+			$author_role = $r['before'] . $role . $r['after'];
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_author_role', $author_role, $r );
+	}
+
+
+/**
+ * Output the title of the forum a topic belongs to
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_forum_title( $topic_id = 0 ) {
+	echo bbp_get_topic_forum_title( $topic_id );
+}
+	/**
+	 * Return the title of the forum a topic belongs to
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic forum title
+	 */
+	function bbp_get_topic_forum_title( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$forum_id = bbp_get_topic_forum_id( $topic_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_forum', bbp_get_forum_title( $forum_id ), $topic_id, $forum_id );
+	}
+
+/**
+ * Output the forum id a topic belongs to
+ *
+ * @since 2.0.0 bbPress (r2491)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_forum_id( $topic_id = 0 ) {
+	echo bbp_get_topic_forum_id( $topic_id );
+}
+	/**
+	 * Return the forum id a topic belongs to
+	 *
+	 * @since 2.0.0 bbPress (r2491)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return int Topic forum id
+	 */
+	function bbp_get_topic_forum_id( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$forum_id = (int) get_post_field( 'post_parent', $topic_id );
+
+		// Meta-data fallback
+		if ( empty( $forum_id ) ) {
+			$forum_id = (int) get_post_meta( $topic_id, '_bbp_forum_id', true );
+		}
+
+		// Filter
+		if ( ! empty( $forum_id ) ) {
+			$forum_id = (int) bbp_get_forum_id( $forum_id );
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_forum_id', $forum_id, $topic_id );
+	}
+
+/**
+ * Output the topics last active ID
+ *
+ * @since 2.0.0 bbPress (r2860)
+ *
+ * @param int $topic_id Optional. Forum id
+ */
+function bbp_topic_last_active_id( $topic_id = 0 ) {
+	echo bbp_get_topic_last_active_id( $topic_id );
+}
+	/**
+	 * Return the topics last active ID
+	 *
+	 * @since 2.0.0 bbPress (r2860)
+	 *
+	 * @param int $topic_id Optional. Forum id
+	 * @return int Forum's last active id
+	 */
+	function bbp_get_topic_last_active_id( $topic_id = 0 ) {
+		$topic_id  = bbp_get_topic_id( $topic_id );
+		$active_id = (int) get_post_meta( $topic_id, '_bbp_last_active_id', true );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_last_active_id', $active_id, $topic_id );
+	}
+
+/**
+ * Output the topics last update date/time (aka freshness)
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_last_active_time( $topic_id = 0 ) {
+	echo bbp_get_topic_last_active_time( $topic_id );
+}
+	/**
+	 * Return the topics last update date/time (aka freshness)
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic freshness
+	 */
+	function bbp_get_topic_last_active_time( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Try to get the most accurate freshness time possible
+		$last_active = get_post_meta( $topic_id, '_bbp_last_active_time', true );
+		if ( empty( $last_active ) ) {
+			$reply_id = bbp_get_topic_last_reply_id( $topic_id );
+			if ( ! empty( $reply_id ) ) {
+				$last_active = get_post_field( 'post_date', $reply_id );
+			} else {
+				$last_active = get_post_field( 'post_date', $topic_id );
+			}
+		}
+
+		$last_active = ! empty( $last_active ) ? bbp_get_time_since( bbp_convert_date( $last_active ) ) : '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_last_active', $last_active, $topic_id );
+	}
+
+/** Topic Subscriptions *******************************************************/
+
+/**
+ * Output the topic subscription link
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+ */
+function bbp_topic_subscription_link( $args = array() ) {
+	echo bbp_get_topic_subscription_link( $args );
+}
+
+	/**
+	 * Get the topic subscription link
+	 *
+	 * A custom wrapper for bbp_get_user_subscribe_link()
+	 *
+	 * @since 2.5.0 bbPress (r5156)
+	 * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+	 */
+	function bbp_get_topic_subscription_link( $args = array() ) {
+
+		// Defaults
+		$retval      = false;
+		$user_id     = bbp_get_current_user_id();
+		$redirect_to = bbp_is_subscriptions()
+			? bbp_get_subscriptions_permalink( $user_id )
+			: '';
+
+		// Parse the arguments
+		$r = bbp_parse_args( $args, array(
+			'user_id'     => $user_id,
+			'object_id'   => bbp_get_topic_id(),
+			'object_type' => 'post',
+			'before'      => '&nbsp;|&nbsp;',
+			'after'       => '',
+			'subscribe'   => esc_html__( 'Subscribe',   'bbpress' ),
+			'unsubscribe' => esc_html__( 'Unsubscribe', 'bbpress' ),
+			'redirect_to' => $redirect_to
+		), 'get_topic_subscribe_link' );
+
+		// Get the link
+		$retval = bbp_get_user_subscribe_link( $r );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_subscribe_link', $retval, $r, $args );
+	}
+
+/** Topic Favorites ***********************************************************/
+
+/**
+ * Output the topic favorite link
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+ */
+function bbp_topic_favorite_link( $args = array() ) {
+	echo bbp_get_topic_favorite_link( $args );
+}
+
+	/**
+	 * Get the forum favorite link
+	 *
+	 * A custom wrapper for bbp_get_user_favorites_link()
+	 *
+	 * @since 2.5.0 bbPress (r5156)
+	 * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+	 */
+	function bbp_get_topic_favorite_link( $args = array() ) {
+
+		// No link
+		$retval      = false;
+		$user_id     = bbp_get_current_user_id();
+		$redirect_to = bbp_is_favorites()
+			? bbp_get_favorites_permalink( $user_id )
+			: '';
+
+		// Parse the arguments
+		$r = bbp_parse_args( $args, array(
+			'user_id'     => $user_id,
+			'object_id'   => bbp_get_topic_id(),
+			'object_type' => 'post',
+			'before'      => '',
+			'after'       => '',
+			'favorite'    => esc_html__( 'Favorite',   'bbpress' ),
+			'favorited'   => esc_html__( 'Unfavorite', 'bbpress' ),
+			'redirect_to' => $redirect_to
+		), 'get_topic_favorite_link' );
+
+		// Get the link
+		$retval = bbp_get_user_favorites_link( $r );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_favorite_link', $retval, $r, $args );
+	}
+
+/** Topic Last Reply **********************************************************/
+
+/**
+ * Output the id of the topics last reply
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_last_reply_id( $topic_id = 0 ) {
+	echo bbp_get_topic_last_reply_id( $topic_id );
+}
+	/**
+	 * Return the id of the topics last reply
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return int Topic last reply id
+	 */
+	function bbp_get_topic_last_reply_id( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$reply_id = (int) get_post_meta( $topic_id, '_bbp_last_reply_id', true );
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_last_reply_id', $reply_id, $topic_id );
+	}
+
+/**
+ * Output the title of the last reply inside a topic
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_last_reply_title( $topic_id = 0 ) {
+	echo bbp_get_topic_last_reply_title( $topic_id );
+}
+	/**
+	 * Return the title of the last reply inside a topic
+	 *
+	 * @since 2.0.0 bbPress (r2753)
+	 * @since 2.6.0 bbPress https://bbpress.trac.wordpress.org/ticket/3039
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic last reply title
+	 */
+	function bbp_get_topic_last_reply_title( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$reply_id = bbp_get_topic_last_reply_id( $topic_id );
+		$retval   = bbp_get_reply_title( $reply_id );
+
+		// Misspelled. Use 'bbp_get_topic_last_reply_title' hook instead.
+		$retval = apply_filters( 'bbp_get_topic_last_topic_title', $retval, $topic_id, $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_last_reply_title', $retval, $topic_id, $reply_id );
+	}
+
+/**
+ * Output the link to the last reply in a topic
+ *
+ * @since 2.0.0 bbPress (r2464)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_last_reply_permalink( $topic_id = 0 ) {
+	echo esc_url( bbp_get_topic_last_reply_permalink( $topic_id ) );
+}
+	/**
+	 * Return the link to the last reply in a topic
+	 *
+	 * @since 2.0.0 bbPress (r2464)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Permanent link to the reply
+	 */
+	function bbp_get_topic_last_reply_permalink( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$reply_id = bbp_get_topic_last_reply_id( $topic_id );
+		$retval   = bbp_get_reply_permalink( $reply_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_last_reply_permalink', $retval, $topic_id, $reply_id );
+	}
+
+/**
+ * Output the link to the last reply in a topic
+ *
+ * @since 2.0.0 bbPress (r2683)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_last_reply_url( $topic_id = 0 ) {
+	echo esc_url( bbp_get_topic_last_reply_url( $topic_id ) );
+}
+	/**
+	 * Return the link to the last reply in a topic
+	 *
+	 * @since 2.0.0 bbPress (r2683)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic last reply url
+	 */
+	function bbp_get_topic_last_reply_url( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$reply_id = bbp_get_topic_last_reply_id( $topic_id );
+
+		if ( ! empty( $reply_id ) && ( $reply_id !== $topic_id ) ) {
+			$reply_url = bbp_get_reply_url( $reply_id );
+		} else {
+			$reply_url = bbp_get_topic_permalink( $topic_id );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_last_reply_url', $reply_url, $topic_id, $reply_id );
+	}
+
+/**
+ * Output link to the most recent activity inside a topic, complete with link
+ * attributes and content.
+ *
+ * @since 2.0.0 bbPress (r2625)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_freshness_link( $topic_id = 0 ) {
+	echo bbp_get_topic_freshness_link( $topic_id );
+}
+	/**
+	 * Returns link to the most recent activity inside a topic, complete
+	 * with link attributes and content.
+	 *
+	 * @since 2.0.0 bbPress (r2625)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic freshness link
+	 */
+	function bbp_get_topic_freshness_link( $topic_id = 0 ) {
+		$topic_id   = bbp_get_topic_id( $topic_id );
+		$link_url   = bbp_get_topic_last_reply_url( $topic_id );
+		$title      = bbp_get_topic_last_reply_title( $topic_id );
+		$time_since = bbp_get_topic_last_active_time( $topic_id );
+
+		if ( ! empty( $time_since ) ) {
+			$anchor = '<a href="' . esc_url( $link_url ) . '" title="' . esc_attr( $title ) . '">' . esc_html( $time_since ) . '</a>';
+		} else {
+			$anchor = esc_html__( 'No Replies', 'bbpress' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_freshness_link', $anchor, $topic_id, $time_since, $link_url, $title );
+	}
+
+/**
+ * Output the replies link of the topic
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_replies_link( $topic_id = 0 ) {
+	echo bbp_get_topic_replies_link( $topic_id );
+}
+
+	/**
+	 * Return the replies link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 */
+	function bbp_get_topic_replies_link( $topic_id = 0 ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$link     = bbp_get_topic_permalink( $topic_id );
+		$replies  = sprintf( _n( '%s reply', '%s replies', bbp_get_topic_reply_count( $topic_id, true ), 'bbpress' ), bbp_get_topic_reply_count( $topic_id, false ) );
+
+		// First link never has view=all
+		$retval = bbp_get_view_all( 'edit_others_replies' )
+			? "<a href='" . esc_url( bbp_remove_view_all( $link ) ) . "'>" . esc_html( $replies ) . "</a>"
+			: $replies;
+
+		// Any deleted replies?
+		$deleted_int = bbp_get_topic_reply_count_hidden( $topic_id, true  );
+
+		// This topic has hidden replies
+		if ( ! empty( $deleted_int ) && current_user_can( 'edit_others_replies' ) ) {
+
+			// Hidden replies
+			$deleted_num = bbp_get_topic_reply_count_hidden( $topic_id, false );
+			$extra       = ' ' . sprintf( _n( '(+%s hidden)', '(+%s hidden)', $deleted_int, 'bbpress' ), $deleted_num );
+
+			// Hidden link
+			$retval .= ! bbp_get_view_all( 'edit_others_replies' )
+				? " <a href='" . esc_url( bbp_add_view_all( $link, true ) ) . "'>" . esc_html( $extra ) . "</a>"
+				: " {$extra}";
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_replies_link', $retval, $topic_id );
+	}
+
+/**
+ * Output total reply count of a topic
+ *
+ * @since 2.0.0 bbPress (r2485)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_topic_reply_count( $topic_id = 0, $integer = false ) {
+	echo bbp_get_topic_reply_count( $topic_id, $integer );
+}
+	/**
+	 * Return total reply count of a topic
+	 *
+	 * @since 2.0.0 bbPress (r2485)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Reply count
+	 */
+	function bbp_get_topic_reply_count( $topic_id = 0, $integer = false ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$replies  = (int) get_post_meta( $topic_id, '_bbp_reply_count', true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_topic_reply_count_int'
+			: 'bbp_get_topic_reply_count';
+
+		return apply_filters( $filter, $replies, $topic_id );
+	}
+
+/**
+ * Output total post count of a topic
+ *
+ * @since 2.0.0 bbPress (r2954)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_topic_post_count( $topic_id = 0, $integer = false ) {
+	echo bbp_get_topic_post_count( $topic_id, $integer );
+}
+	/**
+	 * Return total post count of a topic
+	 *
+	 * @since 2.0.0 bbPress (r2954)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Post count
+	 */
+	function bbp_get_topic_post_count( $topic_id = 0, $integer = false ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$replies  = ( (int) get_post_meta( $topic_id, '_bbp_reply_count', true ) ) + 1;
+		$filter   = ( true === $integer )
+			? 'bbp_get_topic_post_count_int'
+			: 'bbp_get_topic_post_count';
+
+		return apply_filters( $filter, $replies, $topic_id );
+	}
+
+/**
+ * Output total hidden reply count of a topic (hidden includes trashed and
+ * spammed replies)
+ *
+ * @since 2.0.0 bbPress (r2740)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param boolean $integer Optional. Whether or not to format the result
+ */
+function bbp_topic_reply_count_hidden( $topic_id = 0, $integer = false ) {
+	echo bbp_get_topic_reply_count_hidden( $topic_id, $integer );
+}
+	/**
+	 * Return total hidden reply count of a topic (hidden includes trashed
+	 * and spammed replies)
+	 *
+	 * @since 2.0.0 bbPress (r2740)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 * @return int Topic hidden reply count
+	 */
+	function bbp_get_topic_reply_count_hidden( $topic_id = 0, $integer = false ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$replies  = (int) get_post_meta( $topic_id, '_bbp_reply_count_hidden', true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_topic_reply_count_hidden_int'
+			: 'bbp_get_topic_reply_count_hidden';
+
+		return apply_filters( $filter, $replies, $topic_id );
+	}
+
+/**
+ * Output total voice count of a topic
+ *
+ * @since 2.0.0 bbPress (r2567)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_voice_count( $topic_id = 0, $integer = false ) {
+	echo bbp_get_topic_voice_count( $topic_id, $integer );
+}
+	/**
+	 * Return total voice count of a topic
+	 *
+	 * @since 2.0.0 bbPress (r2567)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return int Voice count of the topic
+	 */
+	function bbp_get_topic_voice_count( $topic_id = 0, $integer = false ) {
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$voices   = (int) get_post_meta( $topic_id, '_bbp_voice_count', true );
+		$filter   = ( true === $integer )
+			? 'bbp_get_topic_voice_count_int'
+			: 'bbp_get_topic_voice_count';
+
+		return apply_filters( $filter, $voices, $topic_id );
+	}
+
+/**
+ * Output a the tags of a topic
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param array $args See {@link bbp_get_topic_tag_list()}
+ */
+function bbp_topic_tag_list( $topic_id = 0, $args = array() ) {
+	echo bbp_get_topic_tag_list( $topic_id, $args );
+}
+	/**
+	 * Return the tags of a topic
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param array $args This function supports these arguments:
+	 *  - before: Before the tag list
+	 *  - sep: Tag separator
+	 *  - after: After the tag list
+	 * @return string Tag list of the topic
+	 */
+	function bbp_get_topic_tag_list( $topic_id = 0, $args = array() ) {
+
+		// Bail if topic-tags are off
+		if ( ! bbp_allow_topic_tags() ) {
+			return '';
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'before' => '<div class="bbp-topic-tags"><p>' . esc_html__( 'Tagged:', 'bbpress' ) . '&nbsp;',
+			'sep'    => ', ',
+			'after'  => '</p></div>',
+			'none'   => ''
+		), 'get_topic_tag_list' );
+
+		$topic_id = bbp_get_topic_id( $topic_id );
+
+		// Topic is spammed, so display pre-spam terms
+		if ( bbp_is_topic_spam( $topic_id ) ) {
+
+			// Get pre-spam terms
+			$terms = get_post_meta( $topic_id, '_bbp_spam_topic_tags', true );
+
+			// If terms exist, implode them and compile the return value
+			if ( ! empty( $terms ) ) {
+				$terms = $r['before'] . implode( $r['sep'], $terms ) . $r['after'];
+			}
+
+		// Topic is not spam so display a clickable term list
+		} else {
+			$terms = get_the_term_list( $topic_id, bbp_get_topic_tag_tax_id(), $r['before'], $r['sep'], $r['after'] );
+		}
+
+		// No terms so return none string
+		if ( ! empty( $terms ) ) {
+			$retval = $terms;
+		} else {
+			$retval = $r['none'];
+		}
+
+		return $retval;
+	}
+
+/**
+ * Output the row class of a topic
+ *
+ * @since 2.0.0 bbPress (r2667)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @param array Extra classes you can pass when calling this function
+ */
+function bbp_topic_class( $topic_id = 0, $classes = array() ) {
+	echo bbp_get_topic_class( $topic_id, $classes );
+}
+	/**
+	 * Return the row class of a topic
+	 *
+	 * @since 2.0.0 bbPress (r2667)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @param array Extra classes you can pass when calling this function
+	 * @return string Row class of a topic
+	 */
+	function bbp_get_topic_class( $topic_id = 0, $classes = array() ) {
+		$bbp       = bbpress();
+		$topic_id  = bbp_get_topic_id( $topic_id );
+		$forum_id  = bbp_get_topic_forum_id( $topic_id );
+		$author_id = bbp_get_topic_author_id( $topic_id );
+		$classes   = array_filter( (array) $classes );
+		$count     = isset( $bbp->topic_query->current_post )
+			? (int) $bbp->topic_query->current_post
+			: 1;
+
+		//  Stripes
+		$even_odd = ( $count % 2 )
+			? 'even'
+			: 'odd';
+
+		// Forum moderator replied to topic
+		$forum_moderator = ( bbp_is_user_forum_moderator( $author_id, $forum_id ) === $author_id )
+			? 'forum-mod'
+			: '';
+
+		// Is this topic a sticky?
+		$sticky = bbp_is_topic_sticky( $topic_id, false )
+			? 'sticky'
+			: '';
+
+		// Is this topic a super-sticky?
+		$super_sticky = bbp_is_topic_super_sticky( $topic_id  )
+			? 'super-sticky'
+			: '';
+
+		// Get topic classes
+		$topic_classes = array(
+			'loop-item-'        . $count,
+			'user-id-'          . $author_id,
+			'bbp-parent-forum-' . $forum_id,
+			$even_odd,
+			$forum_moderator,
+			$sticky,
+			$super_sticky
+		);
+
+		// Run the topic classes through the post-class filters, which also
+		// handles the escaping of each individual class.
+		$post_classes = get_post_class( array_merge( $classes, $topic_classes ), $topic_id );
+
+		// Filter
+		$new_classes  = apply_filters( 'bbp_get_topic_class', $post_classes, $topic_id, $classes );
+
+		// Return
+		return 'class="' . implode( ' ', $new_classes ) . '"';
+	}
+
+/** Topic Admin Links *********************************************************/
+
+/**
+ * Output admin links for topic
+ *
+ * @param array $args See {@link bbp_get_topic_admin_links()}
+ */
+function bbp_topic_admin_links( $args = array() ) {
+	echo bbp_get_topic_admin_links( $args );
+}
+	/**
+	 * Return admin links for topic.
+	 *
+	 * Move topic functionality is handled by the edit topic page.
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - id: Optional. Topic id
+	 *  - before: Before the links
+	 *  - after: After the links
+	 *  - sep: Links separator
+	 *  - links: Topic admin links array
+	 * @return string Topic admin links
+	 */
+	function bbp_get_topic_admin_links( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'     => bbp_get_topic_id(),
+			'before' => '<span class="bbp-admin-links">',
+			'after'  => '</span>',
+			'sep'    => ' | ',
+			'links'  => array()
+		), 'get_topic_admin_links' );
+
+		if ( empty( $r['links'] ) ) {
+			$r['links'] = apply_filters( 'bbp_topic_admin_links', array(
+				'edit'    => bbp_get_topic_edit_link   ( $r ),
+				'merge'   => bbp_get_topic_merge_link  ( $r ),
+				'close'   => bbp_get_topic_close_link  ( $r ),
+				'stick'   => bbp_get_topic_stick_link  ( $r ),
+				'trash'   => bbp_get_topic_trash_link  ( $r ),
+				'spam'    => bbp_get_topic_spam_link   ( $r ),
+				'approve' => bbp_get_topic_approve_link( $r ),
+				'reply'   => bbp_get_topic_reply_link  ( $r )
+			), $r['id'] );
+		}
+
+		// See if links need to be unset
+		$topic_status = bbp_get_topic_status( $r['id'] );
+		if ( in_array( $topic_status, bbp_get_non_public_topic_statuses(), true ) ) {
+
+			// Close link shouldn't be visible on trashed/spammed/pending topics
+			unset( $r['links']['close'] );
+
+			// Spam link shouldn't be visible on trashed topics
+			if ( bbp_get_trash_status_id() === $topic_status ) {
+				unset( $r['links']['spam'] );
+
+			// Trash link shouldn't be visible on spam topics
+			} elseif ( bbp_get_spam_status_id() === $topic_status ) {
+				unset( $r['links']['trash'] );
+			}
+		}
+
+		// Process the admin links
+		$links  = implode( $r['sep'], array_filter( $r['links'] ) );
+		$retval = $r['before'] . $links . $r['after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_admin_links', $retval, $r, $args );
+	}
+
+/**
+ * Output the edit link of the topic
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param array $args See {@link bbp_get_topic_edit_link()}
+ */
+function bbp_topic_edit_link( $args = array() ) {
+	echo bbp_get_topic_edit_link( $args );
+}
+
+	/**
+	 * Return the edit link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - edit_text: Edit text
+	 * @return string Topic edit link
+	 */
+	function bbp_get_topic_edit_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'edit_text'    => esc_html__( 'Edit', 'bbpress' )
+		), 'get_topic_edit_link' );
+
+		// Get the topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bypass check if user has caps
+		if ( ! current_user_can( 'edit_others_topics' ) ) {
+
+			// User cannot edit or it is past the lock time
+			if ( empty( $topic ) || ! current_user_can( 'edit_topic', $topic->ID ) || bbp_past_edit_lock( $topic->post_date_gmt ) ) {
+				return;
+			}
+		}
+
+		// Get uri
+		$uri = bbp_get_topic_edit_url( $topic->ID );
+
+		// Bail if no uri
+		if ( empty( $uri ) ) {
+			return;
+		}
+
+		$retval = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-topic-edit-link">' . $r['edit_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_edit_link', $retval, $r, $args );
+	}
+
+/**
+ * Output URL to the topic edit page
+ *
+ * @since 2.0.0 bbPress (r2753)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_edit_url( $topic_id = 0 ) {
+	echo esc_url( bbp_get_topic_edit_url( $topic_id ) );
+}
+	/**
+	 * Return URL to the topic edit page
+	 *
+	 * @since 2.0.0 bbPress (r2753)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 * @return string Topic edit url
+	 */
+	function bbp_get_topic_edit_url( $topic_id = 0 ) {
+
+		$topic = bbp_get_topic( $topic_id );
+		if ( empty( $topic ) ) {
+			return;
+		}
+
+		// Remove view=all link from edit
+		$topic_link = bbp_remove_view_all( bbp_get_topic_permalink( $topic_id ) );
+
+		// Pretty permalinks, previously used `bbp_use_pretty_urls()`
+		// https://bbpress.trac.wordpress.org/ticket/3054
+		if ( false === strpos( $topic_link, '?' ) ) {
+			$url = trailingslashit( $topic_link ) . bbp_get_edit_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_topic_post_type() => $topic->post_name,
+				bbp_get_edit_rewrite_id() => '1'
+			), $topic_link );
+		}
+
+		// Maybe add view=all
+		$url = bbp_add_view_all( $url );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_edit_url', $url, $topic_id );
+	}
+
+/**
+ * Output the trash link of the topic
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param array $args See {@link bbp_get_topic_trash_link()}
+ */
+function bbp_topic_trash_link( $args = array() ) {
+	echo bbp_get_topic_trash_link( $args );
+}
+
+	/**
+	 * Return the trash link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - sep: Links separator
+	 *  - trash_text: Trash text
+	 *  - restore_text: Restore text
+	 *  - delete_text: Delete text
+	 * @return string Topic trash link
+	 */
+	function bbp_get_topic_trash_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'sep'          => ' | ',
+			'trash_text'   => esc_html__( 'Trash',   'bbpress' ),
+			'restore_text' => esc_html__( 'Restore', 'bbpress' ),
+			'delete_text'  => esc_html__( 'Delete',  'bbpress' )
+		), 'get_topic_trash_link' );
+
+		// Get topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or current user cannot delete
+		if ( empty( $topic ) || ! current_user_can( 'delete_topic', $topic->ID ) ) {
+			return;
+		}
+
+		$actions    = array();
+		$trash_days = bbp_get_trash_days( bbp_get_topic_post_type() );
+
+		if ( bbp_is_topic_trash( $topic->ID ) ) {
+			$actions['untrash'] = '<a title="' . esc_attr__( 'Restore this item from the Trash', 'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_topic_trash', 'sub_action' => 'untrash', 'topic_id' => $topic->ID ) ), 'untrash-' . $topic->post_type . '_' . $topic->ID ) ) . '" class="bbp-topic-restore-link">' . $r['restore_text'] . '</a>';
+		} elseif ( ! empty( $trash_days ) ) {
+			$actions['trash']   = '<a title="' . esc_attr__( 'Move this item to the Trash',      'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_topic_trash', 'sub_action' => 'trash',   'topic_id' => $topic->ID ) ), 'trash-'   . $topic->post_type . '_' . $topic->ID ) ) . '" class="bbp-topic-trash-link">'   . $r['trash_text']   . '</a>';
+		}
+
+		if ( bbp_is_topic_trash( $topic->ID ) || empty( $trash_days ) ) {
+			$actions['delete']  = '<a title="' . esc_attr__( 'Delete this item permanently',     'bbpress' ) . '" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'bbp_toggle_topic_trash', 'sub_action' => 'delete',  'topic_id' => $topic->ID ) ), 'delete-'  . $topic->post_type . '_' . $topic->ID ) ) . '" onclick="return confirm(\'' . esc_js( esc_html__( 'Are you sure you want to delete that permanently?', 'bbpress' ) ) . '\' );" class="bbp-topic-delete-link">' . $r['delete_text'] . '</a>';
+		}
+
+		// Process the admin links
+		$retval = $r['link_before'] . implode( $r['sep'], $actions ) . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_trash_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the close link of the topic
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param array $args See {@link bbp_get_topic_close_link()}
+ */
+function bbp_topic_close_link( $args = array() ) {
+	echo bbp_get_topic_close_link( $args );
+}
+
+	/**
+	 * Return the close link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - close_text: Close text
+	 *  - open_text: Open text
+	 * @return string Topic close link
+	 */
+	function bbp_get_topic_close_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'          => 0,
+			'link_before' => '',
+			'link_after'  => '',
+			'sep'         => ' | ',
+			'close_text'  => esc_html_x( 'Close', 'Close the topic', 'bbpress' ),
+			'open_text'   => esc_html_x( 'Open',  'Open the topic', 'bbpress' )
+		), 'get_topic_close_link' );
+
+		// Get topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or current user cannot moderate
+		if ( empty( $topic ) || ! current_user_can( 'moderate', $topic->ID ) ) {
+			return;
+		}
+
+		$display = bbp_is_topic_open( $topic->ID ) ? $r['close_text'] : $r['open_text'];
+		$uri     = add_query_arg( array( 'action' => 'bbp_toggle_topic_close', 'topic_id' => $topic->ID ) );
+		$uri     = wp_nonce_url( $uri, 'close-topic_' . $topic->ID );
+		$retval  = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-topic-close-link">' . $display . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_close_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the approve link of the topic
+ *
+ * @since 2.6.0 bbPress (r5504)
+ *
+ * @param array $args See {@link bbp_get_topic_approve_link()}
+ */
+function bbp_topic_approve_link( $args = array() ) {
+	echo bbp_get_topic_approve_link( $args );
+}
+
+	/**
+	 * Return the approve link of the topic
+	 *
+	 * @since 2.6.0 bbPress (r5504)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - sep: Separator between links
+	 *  - approve_text: Approve text
+	 *  - unapprove_text: Unapprove text
+	 * @return string Topic approve link
+	 */
+	function bbp_get_topic_approve_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'             => 0,
+			'link_before'    => '',
+			'link_after'     => '',
+			'sep'            => ' | ',
+			'approve_text'   => esc_html_x( 'Approve',   'Approve the topic', 'bbpress' ),
+			'unapprove_text' => esc_html_x( 'Unapprove', 'Unapprove the topic', 'bbpress' )
+		), 'get_topic_approve_link' );
+
+		// Get topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or current user cannot moderate
+		if ( empty( $topic ) || ! current_user_can( 'moderate', $topic->ID ) ) {
+			return;
+		}
+
+		$display = bbp_is_topic_pending( $topic->ID ) ? $r['approve_text'] : $r['unapprove_text'];
+		$uri     = add_query_arg( array( 'action' => 'bbp_toggle_topic_approve', 'topic_id' => $topic->ID ) );
+		$uri     = wp_nonce_url( $uri, 'approve-topic_' . $topic->ID );
+		$retval  = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-topic-approve-link">' . $display . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_approve_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the stick link of the topic
+ *
+ * @since 2.0.0 bbPress (r2754)
+ *
+ * @param array $args See {@link bbp_get_topic_stick_link()}
+ */
+function bbp_topic_stick_link( $args = array() ) {
+	echo bbp_get_topic_stick_link( $args );
+}
+
+	/**
+	 * Return the stick link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2754)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - stick_text: Stick text
+	 *  - unstick_text: Unstick text
+	 *  - super_text: Stick to front text
+	 *
+	 * @return string Topic stick link
+	 */
+	function bbp_get_topic_stick_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'stick_text'   => esc_html__( 'Stick',      'bbpress' ),
+			'unstick_text' => esc_html__( 'Unstick',    'bbpress' ),
+			'super_text'   => esc_html__( '(to front)', 'bbpress' ),
+		), 'get_topic_stick_link' );
+
+		// Get topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or current user cannot moderate
+		if ( empty( $topic ) || ! current_user_can( 'moderate', $topic->ID ) ) {
+			return;
+		}
+
+		$is_sticky = bbp_is_topic_sticky( $topic->ID );
+
+		$stick_uri = add_query_arg( array( 'action' => 'bbp_toggle_topic_stick', 'topic_id' => $topic->ID ) );
+		$stick_uri = wp_nonce_url( $stick_uri, 'stick-topic_' . $topic->ID );
+
+		$stick_display = ( true === $is_sticky ) ? $r['unstick_text'] : $r['stick_text'];
+		$stick_display = '<a href="' . esc_url( $stick_uri ) . '" class="bbp-topic-sticky-link">' . $stick_display . '</a>';
+
+		if ( empty( $is_sticky ) ) {
+			$super_uri = add_query_arg( array( 'action' => 'bbp_toggle_topic_stick', 'topic_id' => $topic->ID, 'super' => 1 ) );
+			$super_uri = wp_nonce_url( $super_uri, 'stick-topic_' . $topic->ID );
+
+			$super_display = ' <a href="' . esc_url( $super_uri ) . '" class="bbp-topic-super-sticky-link">' . $r['super_text'] . '</a>';
+		} else {
+			$super_display = '';
+		}
+
+		// Combine the HTML into 1 string
+		$retval = $r['link_before'] . $stick_display . $super_display . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_stick_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the merge link of the topic
+ *
+ * @since 2.0.0 bbPress (r2756)
+ *
+ * @param array $args
+ */
+function bbp_topic_merge_link( $args = array() ) {
+	echo bbp_get_topic_merge_link( $args );
+}
+
+	/**
+	 * Return the merge link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2756)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - merge_text: Merge text
+	 *
+	 * @return string Topic merge link
+	 */
+	function bbp_get_topic_merge_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'merge_text'   => esc_html__( 'Merge', 'bbpress' ),
+		), 'get_topic_merge_link' );
+
+		// Get topic
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or current user cannot moderate
+		if ( empty( $topic ) || ! current_user_can( 'moderate', $topic->ID ) ) {
+			return;
+		}
+
+		$uri    = add_query_arg( array( 'action' => 'merge' ), bbp_get_topic_edit_url( $topic->ID ) );
+		$retval = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-topic-merge-link">' . $r['merge_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_merge_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the spam link of the topic
+ *
+ * @since 2.0.0 bbPress (r2727)
+ *
+ * @param array $args See {@link bbp_get_topic_spam_link()}
+ */
+function bbp_topic_spam_link( $args = array() ) {
+	echo bbp_get_topic_spam_link( $args );
+}
+
+	/**
+	 * Return the spam link of the topic
+	 *
+	 * @since 2.0.0 bbPress (r2727)
+	 *
+	 * @param array $args This function supports these args:
+	 *  - id: Optional. Topic id
+	 *  - link_before: Before the link
+	 *  - link_after: After the link
+	 *  - spam_text: Spam text
+	 *  - unspam_text: Unspam text
+	 *
+	 * @return string Topic spam link
+	 */
+	function bbp_get_topic_spam_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'           => 0,
+			'link_before'  => '',
+			'link_after'   => '',
+			'sep'          => ' | ',
+			'spam_text'    => esc_html__( 'Spam',   'bbpress' ),
+			'unspam_text'  => esc_html__( 'Unspam', 'bbpress' )
+		), 'get_topic_spam_link' );
+
+		$topic = bbp_get_topic( $r['id'] );
+
+		if ( empty( $topic ) || ! current_user_can( 'moderate', $topic->ID ) ) {
+			return;
+		}
+
+		$display = bbp_is_topic_spam( $topic->ID ) ? $r['unspam_text'] : $r['spam_text'];
+		$uri     = add_query_arg( array( 'action' => 'bbp_toggle_topic_spam', 'topic_id' => $topic->ID ) );
+		$uri     = wp_nonce_url( $uri, 'spam-topic_' . $topic->ID );
+		$retval  = $r['link_before'] . '<a href="' . esc_url( $uri ) . '" class="bbp-topic-spam-link">' . $display . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_spam_link', $retval, $r, $args );
+	}
+
+/**
+ * Output the link to go directly to the reply form
+ *
+ * @since 2.4.0 bbPress (r4966)
+ *
+ * @param array $args
+ */
+function bbp_topic_reply_link( $args = array() ) {
+	echo bbp_get_topic_reply_link( $args );
+}
+
+	/**
+	 * Return the link to go directly to the reply form
+	 *
+	 * @since 2.4.0 bbPress (r4966)
+	 *
+	 * @param array $args Arguments
+	 *
+	 * @return string Link for a reply to a topic
+	 */
+	function bbp_get_topic_reply_link( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'id'          => 0,
+			'link_before' => '',
+			'link_after'  => '',
+			'reply_text'  => esc_html_x( 'Reply', 'verb', 'bbpress' ),
+			'add_below'   => 'post',
+			'respond_id'  => 'new-reply-' . bbp_get_topic_id(),
+		), 'get_topic_reply_link' );
+
+		// Get the topic to use it's ID and post_parent
+		$topic = bbp_get_topic( $r['id'] );
+
+		// Bail if no topic or user cannot reply
+		if ( empty( $topic ) || bbp_is_single_reply() || ! bbp_current_user_can_access_create_reply_form() ) {
+			return;
+		}
+
+		// Only add onclick if replies are threaded
+		$onclick = bbp_thread_replies()
+			? ' onclick="return addReply.cancelForm();"'
+			: '';
+
+		// Add $uri to the array, to be passed through the filter
+		$r['uri'] = remove_query_arg( array( 'bbp_reply_to', '_wpnonce' ) ) . '#new-post';
+		$retval   = $r['link_before'] . '<a role="button" href="' . esc_url( $r['uri'] ) . '" class="bbp-topic-reply-link"' . $onclick . '>' . $r['reply_text'] . '</a>' . $r['link_after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_reply_link', $retval, $r, $args );
+	}
+
+/** Topic Pagination **********************************************************/
+
+/**
+ * Return the base URL used inside of pagination links
+ *
+ * @since 2.6.0 bbPress (r6402)
+ *
+ * @param int $forum_id
+ * @return string
+ */
+function bbp_get_topics_pagination_base( $forum_id = 0 ) {
+
+	// If pretty permalinks are enabled, make our pagination pretty
+	if ( bbp_use_pretty_urls() ) {
+
+		// User's topics
+		if ( bbp_is_single_user_topics() ) {
+			$base = bbp_get_user_topics_created_url( bbp_get_displayed_user_id() );
+
+		// User's engagements
+		} elseif ( bbp_is_single_user_engagements() ) {
+			$base = bbp_get_user_engagements_url( bbp_get_displayed_user_id() );
+
+		// User's favorites
+		} elseif ( bbp_is_favorites() ) {
+			$base = bbp_get_favorites_permalink( bbp_get_displayed_user_id() );
+
+		// User's subscriptions
+		} elseif ( bbp_is_subscriptions() ) {
+			$base = bbp_get_subscriptions_permalink( bbp_get_displayed_user_id() );
+
+		// Root profile page
+		} elseif ( bbp_is_single_user() ) {
+			$base = bbp_get_user_profile_url( bbp_get_displayed_user_id() );
+
+		// View
+		} elseif ( bbp_is_single_view() ) {
+			$base = bbp_get_view_url();
+
+		// Topic tag
+		} elseif ( bbp_is_topic_tag() ) {
+			$base = bbp_get_topic_tag_link();
+
+		// Page or single post
+		} elseif ( is_page() || is_single() ) {
+			$base = get_permalink();
+
+		// Forum archive
+		} elseif ( bbp_is_forum_archive() ) {
+			$base = bbp_get_forums_url();
+
+		// Topic archive
+		} elseif ( bbp_is_topic_archive() ) {
+			$base = bbp_get_topics_url();
+
+		// Default
+		} else {
+			$base = get_permalink( $forum_id );
+		}
+
+		// Use pagination base
+		$base = trailingslashit( $base ) . user_trailingslashit( bbp_get_paged_slug() . '/%#%/' );
+
+	// Unpretty pagination
+	} else {
+		$base = add_query_arg( 'paged', '%#%' );
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_topics_pagination_base', $base, $forum_id );
+}
+
+/**
+ * Output the pagination count
+ *
+ * The results are unescaped by design, to allow them to be filtered freely via
+ * the 'bbp_get_forum_pagination_count' filter.
+ *
+ * @since 2.0.0 bbPress (r2519)
+ */
+function bbp_forum_pagination_count() {
+	echo bbp_get_forum_pagination_count();
+}
+	/**
+	 * Return the pagination count
+	 *
+	 * @since 2.0.0 bbPress (r2519)
+	 *
+	 * @return string Forum Pagination count
+	 */
+	function bbp_get_forum_pagination_count() {
+		$bbp = bbpress();
+
+		// Define local variable(s)
+		$retstr = '';
+
+		// Topic query exists
+		if ( ! empty( $bbp->topic_query ) ) {
+
+			// Set pagination values
+			$count_int = intval( $bbp->topic_query->post_count );
+			$start_num = intval( ( $bbp->topic_query->paged - 1 ) * $bbp->topic_query->posts_per_page ) + 1;
+			$total_int = ! empty( $bbp->topic_query->found_posts )
+				? (int) $bbp->topic_query->found_posts
+				: $count_int;
+
+			// Format numbers for display
+			$count_num = bbp_number_format( $count_int );
+			$from_num  = bbp_number_format( $start_num );
+			$total     = bbp_number_format( $total_int );
+			$to_num    = bbp_number_format( ( $start_num + ( $bbp->topic_query->posts_per_page - 1 ) > $bbp->topic_query->found_posts )
+				? $bbp->topic_query->found_posts
+				: $start_num + ( $bbp->topic_query->posts_per_page - 1 ) );
+
+			// Several topics in a forum with a single page
+			if ( empty( $to_num ) ) {
+				$retstr = sprintf( _n( 'Viewing %1$s topic', 'Viewing %1$s topics', $total_int, 'bbpress' ), $total );
+
+			// Several topics in a forum with several pages
+			} else {
+				$retstr = sprintf( _n( 'Viewing topic %2$s (of %4$s total)', 'Viewing %1$s topics - %2$s through %3$s (of %4$s total)', $total_int, 'bbpress' ), $count_num, $from_num, $to_num, $total );
+			}
+
+			// Escape results of _n()
+			$retstr = esc_html( $retstr );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_pagination_count', $retstr );
+	}
+
+/**
+ * Output pagination links
+ *
+ * @since 2.0.0 bbPress (r2519)
+ */
+function bbp_forum_pagination_links() {
+	echo bbp_get_forum_pagination_links();
+}
+	/**
+	 * Return pagination links
+	 *
+	 * @since 2.0.0 bbPress (r2519)
+	 *
+	 * @return string Pagination links
+	 */
+	function bbp_get_forum_pagination_links() {
+		$bbp = bbpress();
+
+		if ( empty( $bbp->topic_query ) ) {
+			return false;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_forum_pagination_links', $bbp->topic_query->pagination_links );
+	}
+
+/**
+ * Displays topic notices
+ *
+ * @since 2.0.0 bbPress (r2744)
+ */
+function bbp_topic_notices() {
+
+	// Bail if not viewing a topic
+	if ( ! bbp_is_single_topic() ) {
+		return;
+	}
+
+	// Get the topic_status
+	$topic_status = bbp_get_topic_status();
+
+	// Get the topic status
+	switch ( $topic_status ) {
+
+		// Spam notice
+		case bbp_get_spam_status_id() :
+			$notice_text = esc_html__( 'This topic is marked as spam.', 'bbpress' );
+			break;
+
+		// Trashed notice
+		case bbp_get_trash_status_id() :
+			$notice_text = esc_html__( 'This topic is in the trash.', 'bbpress' );
+			break;
+
+		// Pending notice
+		case bbp_get_pending_status_id() :
+			$notice_text = esc_html__( 'This topic is pending moderation.', 'bbpress' );
+			break;
+
+		// Standard status
+		default :
+			$notice_text = '';
+			break;
+	}
+
+	// Filter notice text and bail if empty
+	$notice_text = apply_filters( 'bbp_topic_notices', $notice_text, $topic_status, bbp_get_topic_id() );
+	if ( empty( $notice_text ) ) {
+		return;
+	}
+
+	bbp_add_error( 'topic_notice', $notice_text, 'message' );
+}
+
+/**
+ * Displays topic type select box (normal/sticky/super sticky)
+ *
+ * @since 2.0.0 bbPress (r2784)
+ *
+ * @deprecated 2.4.0 bbPress (r5059)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_stick_topic
+ *  - tab: Deprecated. Tabindex
+ *  - topic_id: Topic id
+ *  - selected: Override the selected option
+ */
+function bbp_topic_type_select( $args = array() ) {
+	echo bbp_get_form_topic_type_dropdown( $args );
+}
+
+/**
+ * Displays topic type select box (normal/sticky/super sticky)
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_stick_topic
+ *  - tab: Deprecated. Tabindex
+ *  - topic_id: Topic id
+ *  - selected: Override the selected option
+ */
+function bbp_form_topic_type_dropdown( $args = array() ) {
+	echo bbp_get_form_topic_type_dropdown( $args );
+}
+	/**
+	 * Returns topic type select box (normal/sticky/super sticky)
+	 *
+	 * @since 2.4.0 bbPress (r5059)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_stick_topic
+	 *  - tab: Deprecated. Tabindex
+	 *  - topic_id: Topic id
+	 *  - selected: Override the selected option
+	 */
+	function bbp_get_form_topic_type_dropdown( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_stick_topic',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'topic_id'     => 0,
+			'selected'     => false
+		), 'topic_type_select' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_topic_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value passed
+			} else {
+
+				// Edit topic
+				if ( bbp_is_single_topic() || bbp_is_topic_edit() ) {
+
+					// Get current topic id
+					$r['topic_id'] = bbp_get_topic_id( $r['topic_id'] );
+
+					// Topic is super sticky
+					if ( bbp_is_topic_super_sticky( $r['topic_id'] ) ) {
+						$r['selected'] = 'super';
+
+					// Topic is sticky or normal
+					} else {
+						$r['selected'] = bbp_is_topic_sticky( $r['topic_id'], false )
+							? 'stick'
+							: 'unstick';
+					}
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ); ?>" id="<?php echo esc_attr( $r['select_id'] ); ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_topic_types( $r['topic_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_type_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/**
+ * Output value topic status dropdown
+ *
+ * @since 2.4.0 bbPress (r5059)
+ *
+ * @param $args This function supports these arguments:
+ *  - select_id: Select id. Defaults to bbp_topic_status
+ *  - tab: Deprecated. Tabindex
+ *  - topic_id: Topic id
+ *  - selected: Override the selected option
+ */
+function bbp_form_topic_status_dropdown( $args = array() ) {
+	echo bbp_get_form_topic_status_dropdown( $args );
+}
+	/**
+	 * Returns topic status dropdown
+	 *
+	 * This dropdown is only intended to be seen by users with the 'moderate'
+	 * capability. Because of this, no additional capability checks are performed
+	 * within this function to check available topic statuses.
+	 *
+	 * @since 2.4.0 bbPress (r5059)
+	 *
+	 * @param $args This function supports these arguments:
+	 *  - select_id: Select id. Defaults to bbp_topic_status
+	 *  - tab: Deprecated. Tabindex
+	 *  - topic_id: Topic id
+	 *  - selected: Override the selected option
+	 */
+	function bbp_get_form_topic_status_dropdown( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'select_id'    => 'bbp_topic_status',
+			'select_class' => 'bbp_dropdown',
+			'tab'          => false,
+			'topic_id'     => 0,
+			'selected'     => false
+		), 'topic_open_close_select' );
+
+		// No specific selected value passed
+		if ( empty( $r['selected'] ) ) {
+
+			// Post value is passed
+			if ( bbp_is_topic_form_post_request() && isset( $_POST[ $r['select_id'] ] ) ) {
+				$r['selected'] = sanitize_key( $_POST[ $r['select_id'] ] );
+
+			// No Post value was passed
+			} else {
+
+				// Edit topic
+				if ( bbp_is_topic_edit() ) {
+					$r['topic_id'] = bbp_get_topic_id( $r['topic_id'] );
+					$r['selected'] = bbp_get_topic_status( $r['topic_id'] );
+
+				// New topic
+				} else {
+					$r['selected'] = bbp_get_public_status_id();
+				}
+			}
+		}
+
+		// Start an output buffer, we'll finish it after the select loop
+		ob_start(); ?>
+
+		<select name="<?php echo esc_attr( $r['select_id'] ) ?>" id="<?php echo esc_attr( $r['select_id'] ); ?>_select" class="<?php echo esc_attr( $r['select_class'] ); ?>"<?php bbp_tab_index_attribute( $r['tab'] ); ?>>
+
+			<?php foreach ( bbp_get_topic_statuses( $r['topic_id'] ) as $key => $label ) : ?>
+
+				<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $key, $r['selected'] ); ?>><?php echo esc_html( $label ); ?></option>
+
+			<?php endforeach; ?>
+
+		</select>
+
+		<?php
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_status_dropdown', ob_get_clean(), $r, $args );
+	}
+
+/** Single Topic **************************************************************/
+
+/**
+ * Output a fancy description of the current topic, including total topics,
+ * total replies, and last activity.
+ *
+ * @since 2.0.0 bbPress (r2860)
+ *
+ * @param array $args See {@link bbp_get_single_topic_description()}
+ */
+function bbp_single_topic_description( $args = array() ) {
+	echo bbp_get_single_topic_description( $args );
+}
+	/**
+	 * Return a fancy description of the current topic, including total topics,
+	 * total replies, and last activity.
+	 *
+	 * @since 2.0.0 bbPress (r2860)
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - topic_id: Topic id
+	 *  - before: Before the text
+	 *  - after: After the text
+	 *  - size: Size of the avatar
+	 * @return string Filtered topic description
+	 */
+	function bbp_get_single_topic_description( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'topic_id'  => 0,
+			'before'    => '<div class="bbp-template-notice info"><ul><li class="bbp-topic-description">',
+			'after'     => '</li></ul></div>',
+			'size'      => 14
+		), 'get_single_topic_description' );
+
+		// Validate topic_id
+		$topic_id = bbp_get_topic_id( $r['topic_id'] );
+
+		// Unhook the 'view all' query var adder
+		remove_filter( 'bbp_get_topic_permalink', 'bbp_add_view_all' );
+
+		// Build the topic description
+		$vc_int      = bbp_get_topic_voice_count   ( $topic_id, true  );
+		$voice_count = bbp_get_topic_voice_count   ( $topic_id, false );
+		$reply_count = bbp_get_topic_replies_link  ( $topic_id        );
+		$time_since  = bbp_get_topic_freshness_link( $topic_id        );
+
+		// Singular/Plural
+		$voice_count = sprintf( _n( '%s voice', '%s voices', $vc_int, 'bbpress' ), $voice_count );
+
+		// Topic has activity (could be from reply or topic author)
+		$last_active = bbp_get_topic_last_active_id( $topic_id );
+		if ( ! empty( $vc_int ) && ! empty( $last_active ) ) {
+			$last_updated_by = bbp_get_author_link( array( 'post_id' => $last_active, 'size' => $r['size'] ) );
+			$retstr          = sprintf( esc_html__( 'This topic has %1$s, %2$s, and was last updated %3$s by %4$s.', 'bbpress' ), $reply_count, $voice_count, $time_since, $last_updated_by );
+
+		// Topic has no replies
+		} elseif ( ! empty( $vc_int ) && ! empty( $reply_count ) ) {
+			$retstr = sprintf( esc_html__( 'This topic has %1$s and %2$s.', 'bbpress' ), $voice_count, $reply_count );
+
+		// Topic has no replies and no voices
+		} elseif ( empty( $vc_int ) && empty( $reply_count ) ) {
+			$retstr = esc_html__( 'This topic has no replies.', 'bbpress' );
+
+		// Topic is pending
+		} elseif ( bbp_get_topic_status( $topic_id ) === bbp_get_pending_status_id() ) {
+			$retstr = esc_html__( 'This topic is pending moderation.', 'bbpress' );
+
+		// Fallback
+		} else {
+			$retstr = esc_html__( 'This topic is empty.', 'bbpress' );
+		}
+
+		// Add the 'view all' filter back
+		add_filter( 'bbp_get_topic_permalink', 'bbp_add_view_all' );
+
+		// Combine the elements together
+		$retstr = $r['before'] . $retstr . $r['after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_single_topic_description', $retstr, $r, $args );
+	}
+
+/** Topic Tags ****************************************************************/
+
+/**
+ * Output the unique id of the topic tag taxonomy
+ *
+ * @since 2.0.0 bbPress (r3348)
+ */
+function bbp_topic_tag_tax_id() {
+	echo bbp_get_topic_tag_tax_id();
+}
+	/**
+	 * Return the unique id of the topic tag taxonomy
+	 *
+	 * @since 2.0.0 bbPress (r3348)
+	 *
+	 * @return string The unique topic tag taxonomy
+	 */
+	function bbp_get_topic_tag_tax_id() {
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_tax_id', bbpress()->topic_tag_tax_id );
+	}
+
+/**
+ * Return array of labels used by the topic-tag taxonomy
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_topic_tag_tax_labels() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_tag_tax_labels', array(
+		'name'                       => esc_attr__( 'Topic Tags',                      'bbpress' ),
+		'menu_name'                  => esc_attr__( 'Topic Tags',                      'bbpress' ),
+		'singular_name'              => esc_attr__( 'Topic Tag',                       'bbpress' ),
+		'search_items'               => esc_attr__( 'Search Tags',                     'bbpress' ),
+		'popular_items'              => esc_attr__( 'Popular Tags',                    'bbpress' ),
+		'all_items'                  => esc_attr__( 'All Tags',                        'bbpress' ),
+		'parent_item'                => esc_attr__( 'Parent Tag',                      'bbpress' ),
+		'parent_item_colon'          => esc_attr__( 'Parent Tag:',                     'bbpress' ),
+		'edit_item'                  => esc_attr__( 'Edit Tag',                        'bbpress' ),
+		'view_item'                  => esc_attr__( 'View Topic Tag',                  'bbpress' ),
+		'update_item'                => esc_attr__( 'Update Tag',                      'bbpress' ),
+		'add_new_item'               => esc_attr__( 'Add New Tag',                     'bbpress' ),
+		'new_item_name'              => esc_attr__( 'New Tag Name',                    'bbpress' ),
+		'separate_items_with_commas' => esc_attr__( 'Separate topic tags with commas', 'bbpress' ),
+		'add_or_remove_items'        => esc_attr__( 'Add or remove tags',              'bbpress' ),
+		'choose_from_most_used'      => esc_attr__( 'Choose from the most used tags',  'bbpress' ),
+		'not_found'                  => esc_attr__( 'No topic tags found.',            'bbpress' ),
+		'no_terms'                   => esc_attr__( 'No topic tags',                   'bbpress' ),
+		'items_list_navigation'      => esc_attr__( 'Topic tags list navigation',      'bbpress' ),
+		'items_list'                 => esc_attr__( 'Topic tags list',                 'bbpress' ),
+		'most_used'                  => esc_attr__( 'Most used topic tags',            'bbpress' ),
+		'back_to_items'              => esc_attr__( '&larr; Back to Tags',             'bbpress' )
+	) );
+}
+
+/**
+ * Return an array of topic-tag taxonomy rewrite settings
+ *
+ * @since 2.5.0 bbPress (r5129)
+ *
+ * @return array
+ */
+function bbp_get_topic_tag_tax_rewrite() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_tag_tax_rewrite', array(
+		'slug'       => bbp_get_topic_tag_tax_slug(),
+		'with_front' => false
+	) );
+}
+
+/**
+ * Output the id of the current tag
+ *
+ * @since 2.0.0 bbPress (r3109)
+ *
+ */
+function bbp_topic_tag_id( $tag = '' ) {
+	echo bbp_get_topic_tag_id( $tag );
+}
+	/**
+	 * Return the id of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3109)
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_id( $tag = '' ) {
+
+		// Get the term
+		if ( ! empty( $tag ) ) {
+			$term = get_term_by( 'slug', $tag, bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Get the term ID
+		$retval = ! empty( $term->term_id )
+			? $term->term_id
+			: 0;
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_topic_tag_id', (int) $retval, $tag, $term );
+	}
+
+/**
+ * Output the name of the current tag
+ *
+ * @since 2.0.0 bbPress (r3109)
+ */
+function bbp_topic_tag_name( $tag = '' ) {
+	echo bbp_get_topic_tag_name( $tag );
+}
+	/**
+	 * Return the name of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3109)
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_name( $tag = '' ) {
+
+		// Get the term
+		if ( ! empty( $tag ) ) {
+			$term = get_term_by( 'slug', $tag, bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Get the term name
+		$retval = ! empty( $term->name )
+			? $term->name
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_name', $retval, $tag, $term );
+	}
+
+/**
+ * Output the slug of the current tag
+ *
+ * @since 2.0.0 bbPress (r3109)
+ */
+function bbp_topic_tag_slug( $tag = '' ) {
+	echo bbp_get_topic_tag_slug( $tag );
+}
+	/**
+	 * Return the slug of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3109)
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_slug( $tag = '' ) {
+
+		// Get the term
+		if ( ! empty( $tag ) ) {
+			$term = get_term_by( 'slug', $tag, bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Get the term slug
+		$retval = ! empty( $term->slug )
+			? $term->slug
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_slug', $retval, $tag, $term );
+	}
+
+/**
+ * Output the link of the current tag
+ *
+ * @since 2.0.0 bbPress (r3348)
+ */
+function bbp_topic_tag_link( $tag = '' ) {
+	echo esc_url( bbp_get_topic_tag_link( $tag ) );
+}
+	/**
+	 * Return the link of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3348)
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_link( $tag = '' ) {
+
+		// Get the term
+		if ( ! empty( $tag ) ) {
+			$term = get_term_by( 'slug', $tag, bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Get the term link
+		$retval = ! empty( $term->term_id )
+			? get_term_link( $term, bbp_get_topic_tag_tax_id() )
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_link', $retval, $tag, $term );
+	}
+
+/**
+ * Output the link of the current tag
+ *
+ * @since 2.0.0 bbPress (r3348)
+ */
+function bbp_topic_tag_edit_link( $tag = '' ) {
+	echo esc_url( bbp_get_topic_tag_edit_link( $tag ) );
+}
+	/**
+	 * Return the link of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3348)
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_edit_link( $tag = '' ) {
+
+		// Get the term
+		if ( ! empty( $tag ) ) {
+			$term = get_term_by( 'slug', $tag, bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Get the term's edit link
+		if ( ! empty( $term->term_id ) ) {
+
+			// Pretty or ugly URL
+			$retval = bbp_use_pretty_urls()
+				? user_trailingslashit( trailingslashit( bbp_get_topic_tag_link() ) . bbp_get_edit_slug() )
+				: add_query_arg( array( bbp_get_edit_rewrite_id() => '1' ), bbp_get_topic_tag_link() );
+
+		// No link
+		} else {
+			$retval = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_edit_link', $retval, $tag, $term );
+	}
+
+/**
+ * Output the description of the current tag
+ *
+ * @since 2.0.0 bbPress (r3109)
+ */
+function bbp_topic_tag_description( $args = array() ) {
+	echo bbp_get_topic_tag_description( $args );
+}
+	/**
+	 * Return the description of the current tag
+	 *
+	 * @since 2.0.0 bbPress (r3109)
+	 *
+	 * @param array $args before|after|tag
+	 *
+	 * @return string Term Name
+	 */
+	function bbp_get_topic_tag_description( $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'before' => '<div class="bbp-topic-tag-description"><p>',
+			'after'  => '</p></div>',
+			'tag'    => ''
+		), 'get_topic_tag_description' );
+
+		// Get the term
+		if ( ! empty( $r['tag'] ) ) {
+			$term = get_term_by( 'slug', $r['tag'], bbp_get_topic_tag_tax_id() );
+		} else {
+			$tag  = $r['tag'] = get_query_var( 'term' );
+			$term = get_queried_object();
+		}
+
+		// Add before & after if description exists
+		$retval = ! empty( $term->description )
+			? $r['before'] . $term->description . $r['after']
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_tag_description', $retval, $r, $args, $tag, $term );
+	}
+
+/** Forms *********************************************************************/
+
+/**
+ * Output the value of topic title field
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_title() {
+	echo bbp_get_form_topic_title();
+}
+	/**
+	 * Return the value of topic title field
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Value of topic title field
+	 */
+	function bbp_get_form_topic_title() {
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_topic_title'] ) ) {
+			$topic_title = wp_unslash( $_POST['bbp_topic_title'] );
+
+		// Get edit data
+		} elseif ( bbp_is_topic_edit() ) {
+			$topic_title = bbp_get_global_post_field( 'post_title', 'raw' );
+
+		// No data
+		} else {
+			$topic_title = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_title', $topic_title );
+	}
+
+/**
+ * Output the value of topic content field
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_content() {
+	echo bbp_get_form_topic_content();
+}
+	/**
+	 * Return the value of topic content field
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Value of topic content field
+	 */
+	function bbp_get_form_topic_content() {
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_topic_content'] ) ) {
+			$topic_content = wp_unslash( $_POST['bbp_topic_content'] );
+
+		// Get edit data
+		} elseif ( bbp_is_topic_edit() ) {
+			$topic_content = bbp_get_global_post_field( 'post_content', 'raw' );
+
+		// No data
+		} else {
+			$topic_content = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_content', $topic_content );
+	}
+
+/**
+ * Allow topic rows to have administrative actions
+ *
+ * @since 2.1.0 bbPress (r3653)
+ *
+ * @todo Links and filter
+ */
+function bbp_topic_row_actions() {
+	do_action( 'bbp_topic_row_actions' );
+}
+
+/**
+ * Output value of topic tags field
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_tags() {
+	echo bbp_get_form_topic_tags();
+}
+	/**
+	 * Return value of topic tags field
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Value of topic tags field
+	 */
+	function bbp_get_form_topic_tags() {
+
+		// Default return value
+		$topic_tags = '';
+
+		// Get _POST data
+		if ( ( bbp_is_topic_form_post_request() || bbp_is_reply_form_post_request() ) && isset( $_POST['bbp_topic_tags'] ) ) {
+			$topic_tags = wp_unslash( $_POST['bbp_topic_tags'] );
+
+		// Get edit data
+		} elseif ( bbp_is_single_topic() || bbp_is_single_reply() || bbp_is_topic_edit() || bbp_is_reply_edit() ) {
+
+			// Determine the topic id based on the post type
+			switch ( get_post_type() ) {
+
+				// Post is a topic
+				case bbp_get_topic_post_type() :
+					$topic_id = bbp_get_topic_id( get_the_ID() );
+					break;
+
+				// Post is a reply
+				case bbp_get_reply_post_type() :
+					$topic_id = bbp_get_reply_topic_id( get_the_ID() );
+					break;
+			}
+
+			// Topic exists
+			if ( ! empty( $topic_id ) ) {
+
+				// Topic is spammed so display pre-spam terms
+				if ( bbp_is_topic_spam( $topic_id ) ) {
+
+					// Get pre-spam terms
+					$spam_terms = get_post_meta( $topic_id, '_bbp_spam_topic_tags', true );
+					$topic_tags = ( ! empty( $spam_terms ) ) ? implode( ', ', $spam_terms ) : '';
+
+				// Topic is not spam so get real terms
+				} else {
+					$topic_tags = bbp_get_topic_tag_names( $topic_id );
+				}
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_tags', $topic_tags );
+	}
+
+/**
+ * Output value of topic forum
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_forum() {
+	echo bbp_get_form_topic_forum();
+}
+	/**
+	 * Return value of topic forum
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Value of topic content field
+	 */
+	function bbp_get_form_topic_forum() {
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_forum_id'] ) ) {
+			$topic_forum = (int) $_POST['bbp_forum_id'];
+
+		// Get edit data
+		} elseif ( bbp_is_topic_edit() ) {
+			$topic_forum = bbp_get_topic_forum_id();
+
+		// No data
+		} else {
+			$topic_forum = 0;
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_forum', $topic_forum );
+	}
+
+/**
+ * Output checked value of topic subscription
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_subscribed() {
+	echo bbp_get_form_topic_subscribed();
+}
+	/**
+	 * Return checked value of topic subscription
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Checked value of topic subscription
+	 */
+	function bbp_get_form_topic_subscribed() {
+
+		// Default value
+		$topic_subscribed = false;
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_topic_subscription'] ) ) {
+			$topic_subscribed = (bool) $_POST['bbp_topic_subscription'];
+
+		// Get edit data
+		} elseif ( bbp_is_topic_edit() || bbp_is_reply_edit() ) {
+			$post_author      = (int) bbp_get_global_post_field( 'post_author', 'raw' );
+			$topic_subscribed = bbp_is_user_subscribed( $post_author, bbp_get_topic_id() );
+
+		// Get current status
+		} elseif ( bbp_is_single_topic() ) {
+			$topic_subscribed = bbp_is_user_subscribed( bbp_get_current_user_id(), bbp_get_topic_id() );
+		}
+
+		// Get checked output
+		$checked = checked( $topic_subscribed, true, false );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_subscribed', $checked, $topic_subscribed );
+	}
+
+/**
+ * Output checked value of topic log edit field
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_log_edit() {
+	echo bbp_get_form_topic_log_edit();
+}
+	/**
+	 * Return checked value of topic log edit field
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Topic log edit checked value
+	 */
+	function bbp_get_form_topic_log_edit() {
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_log_topic_edit'] ) ) {
+			$topic_revision = (bool) $_POST['bbp_log_topic_edit'];
+
+		// No data
+		} else {
+			$topic_revision = true;
+		}
+
+		// Get checked output
+		$checked = checked( $topic_revision, true, false );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_log_edit', $checked, $topic_revision );
+	}
+
+/**
+ * Output the value of the topic edit reason
+ *
+ * @since 2.0.0 bbPress (r2976)
+ */
+function bbp_form_topic_edit_reason() {
+	echo bbp_get_form_topic_edit_reason();
+}
+	/**
+	 * Return the value of the topic edit reason
+	 *
+	 * @since 2.0.0 bbPress (r2976)
+	 *
+	 * @return string Topic edit reason value
+	 */
+	function bbp_get_form_topic_edit_reason() {
+
+		// Get _POST data
+		if ( bbp_is_topic_form_post_request() && isset( $_POST['bbp_topic_edit_reason'] ) ) {
+			$topic_edit_reason = wp_unslash( $_POST['bbp_topic_edit_reason'] );
+
+		// No data
+		} else {
+			$topic_edit_reason = '';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_form_topic_edit_reason', $topic_edit_reason );
+	}
+
+/**
+ * Verify if a POST request came from a failed topic attempt.
+ *
+ * Used to avoid cross-site request forgeries when checking posted topic form
+ * content.
+ *
+ * @see bbp_topic_form_fields()
+ *
+ * @since 2.6.0 bbPress (r5558)
+ *
+ * @return boolean True if is a post request with valid nonce
+ */
+function bbp_is_topic_form_post_request() {
+
+	// Bail if not a post request
+	if ( ! bbp_is_post_request() ) {
+		return false;
+	}
+
+	// Creating a new topic
+	if ( bbp_verify_nonce_request( 'bbp-new-topic' ) ) {
+		return true;
+	}
+
+	// Editing an existing topic
+	if ( bbp_verify_nonce_request( 'bbp-edit-topic_' . bbp_get_topic_id() ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/** Warning *******************************************************************/
+
+/**
+ * Should the topic-lock alert appear?
+ *
+ * @since 2.6.0 bbPress (r6342)
+ *
+ * @return bool
+ */
+function bbp_show_topic_lock_alert() {
+
+	// Default to not showing the alert
+	$retval = false;
+
+	// Get the current topic ID
+	$topic_id = bbp_get_topic_id();
+
+	// Only show on single topic pages
+	if ( bbp_is_topic_edit() || bbp_is_single_topic() ) {
+
+		// Only show to moderators
+		if ( current_user_can( 'moderate', $topic_id ) ) {
+
+			// Locked?
+			$user_id = bbp_check_post_lock( $topic_id );
+
+			// Only show if not locked by the current user
+			if ( ! empty( $user_id ) && ( bbp_get_current_user_id() !== $user_id ) ) {
+				$retval = true;
+			}
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_show_topic_lock_alert', $retval, $topic_id );
+}
+
+/**
+ * Output the topic lock description
+ *
+ * @since 2.6.0 bbPress (r6343)
+ *
+ * @param int $topic_id Optional. Topic id
+ */
+function bbp_topic_lock_description( $topic_id = 0 ) {
+	echo bbp_get_topic_lock_description( $topic_id );
+}
+	/**
+	 * Return the topic lock description
+	 *
+	 * @since 2.6.0 bbPress (r6343)
+	 *
+	 * @param int $topic_id Optional. Topic id
+	 */
+	function bbp_get_topic_lock_description( $topic_id = 0 ) {
+
+		// Check if topic is edit locked
+		$topic_id = bbp_get_topic_id( $topic_id );
+		$user_id  = bbp_check_post_lock( $topic_id );
+		$person   = empty( $user_id )
+			? esc_html__( 'Nobody', 'bbpress' )
+			: bbp_get_user_profile_link( $user_id );
+
+		// Get the text
+		$text = sprintf( esc_html__( '%1$s is currently editing this topic.', 'bbpress' ), $person );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_topic_lock_description', $text, $user_id, $topic_id );
+	}
Index: /tags/2.6.6/src/includes/users/capabilities.php
===================================================================
--- /tags/2.6.6/src/includes/users/capabilities.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/capabilities.php	(revision 7162)
@@ -0,0 +1,860 @@
+<?php
+
+/**
+ * bbPress User Capabilites
+ *
+ * Used to map user capabilities to WordPress's existing capabilities.
+ *
+ * @package bbPress
+ * @subpackage Capabilities
+ */
+
+/**
+ * Maps primary capabilities
+ *
+ * @since 2.2.0 bbPress (r4242)
+ *
+ * @param array  $caps Capabilities for meta capability.
+ * @param string $cap Capability name.
+ * @param int    $user_id User id.
+ * @param array  $args Arguments.
+ *
+ * @return array Actual capabilities for meta capability
+ */
+function bbp_map_primary_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+		case 'spectate' :
+
+			// Do not allow inactive users.
+			if ( bbp_is_user_inactive( $user_id ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Default to the current cap.
+			} else {
+				$caps = array( $cap );
+			}
+			break;
+
+		case 'participate' :
+
+			// Do not allow inactive users.
+			if ( bbp_is_user_inactive( $user_id ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Default to the current cap.
+			} else {
+				$caps = array( $cap );
+			}
+			break;
+
+		case 'moderate' :
+
+			// Do not allow inactive users.
+			if ( bbp_is_user_inactive( $user_id ) ) {
+				$caps = array( 'do_not_allow' );
+
+			// Keymasters can always moderate.
+			} elseif ( bbp_is_user_keymaster( $user_id ) ) {
+				$caps = array( 'spectate' );
+
+			// Check if user can moderate forum.
+			} elseif ( bbp_allow_forum_mods() ) {
+				$caps = array( $cap );
+
+				// Bail if no post to check.
+				if ( empty( $args[0] ) ) {
+					break;
+				}
+
+				// Get the post.
+				$_post = get_post( $args[0] );
+				if ( empty( $_post ) ) {
+					break;
+				}
+
+				// Get forum ID for specific type of post.
+				switch ( $_post->post_type ) {
+
+					// Forum.
+					case bbp_get_forum_post_type() :
+						$forum_id = bbp_get_forum_id( $_post->ID );
+						break;
+
+					// Topic.
+					case bbp_get_topic_post_type() :
+						$forum_id = bbp_get_topic_forum_id( $_post->ID );
+						break;
+
+					// Reply.
+					case bbp_get_reply_post_type() :
+						$forum_id = bbp_get_reply_forum_id( $_post->ID );
+						break;
+
+					// Any other post type defaults to 0.
+					default :
+						$forum_id = 0;
+						break;
+				}
+
+				// Bail if no forum ID.
+				if ( empty( $forum_id ) ) {
+					break;
+				}
+
+				// User is mod of this forum
+				if ( bbp_is_object_of_user( $forum_id, $user_id, '_bbp_moderator_id' ) ) {
+					$caps = array( 'spectate' );
+				}
+			}
+
+			break;
+
+		/** Super Moderators **************************************************/
+
+		case 'edit_user'  :
+		case 'edit_users' :
+
+			// Moderators can edit users if super moderators is enabled
+			if ( bbp_allow_super_mods() ) {
+
+				// Get the user ID
+				$_user_id = ! empty( $args[0] )
+					? (int) $args[0]
+					: bbp_get_displayed_user_id();
+
+				// Users can always edit themselves, so only map for others
+				if ( ! empty( $_user_id ) && ( $_user_id !== $user_id ) ) {
+
+					// Super moderators cannot edit keymasters
+					if ( ! bbp_is_user_keymaster( $_user_id ) ) {
+						$caps = array( 'moderate' );
+					}
+				}
+			}
+
+			break;
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_map_primary_meta_caps', $caps, $cap, $user_id, $args );
+}
+
+/**
+ * Set a user's role in the forums
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @param int $user_id
+ *
+ * @return mixed False if no change. String of new role if changed.
+ */
+function bbp_set_user_role( $user_id = 0, $new_role = '' ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id, false, false );
+	$user    = get_userdata( $user_id );
+
+	// User exists
+	if ( ! empty( $user ) ) {
+
+		// Get user forum role
+		$role = bbp_get_user_role( $user_id );
+
+		// User already has this role so no new role is set
+		if ( $new_role === $role ) {
+			$new_role = false;
+
+		// User role is different than the new (valid) role
+		} elseif ( bbp_is_valid_role( $new_role ) ) {
+
+			// Remove the old role
+			if ( ! empty( $role ) ) {
+				$user->remove_role( $role );
+			}
+
+			// Add the new role
+			if ( ! empty( $new_role ) ) {
+				$user->add_role( $new_role );
+			}
+		}
+
+	// User does don exist so return false
+	} else {
+		$new_role = false;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_set_user_role', $new_role, $user_id, $user );
+}
+
+/**
+ * Return a user's forums role
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @param int $user_id
+ *
+ * @return string
+ */
+function bbp_get_user_role( $user_id = 0 ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	$user    = get_userdata( $user_id );
+	$role    = false;
+
+	// User has roles so look for a bbPress one
+	if ( ! empty( $user->roles ) ) {
+
+		// Look for a bbPress role
+		$roles = array_intersect(
+			array_values( $user->roles ),
+			array_keys( bbp_get_dynamic_roles() )
+		);
+
+		// If there's a role in the array, use the first one. This isn't very
+		// smart, but since roles aren't exactly hierarchical, and bbPress
+		// does not yet have a UI for multiple user roles, it's fine for now.
+		if ( ! empty( $roles ) ) {
+			$role = array_shift( $roles );
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_role', $role, $user_id, $user );
+}
+
+/**
+ * Return a user's blog role
+ *
+ * @since 2.3.0 bbPress (r4446)
+ *
+ * @param int $user_id
+ *
+ * @return string
+ */
+function bbp_get_user_blog_role( $user_id = 0 ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	$user    = get_userdata( $user_id );
+	$role    = false;
+
+	// User has roles so lets
+	if ( ! empty( $user->roles ) ) {
+
+		// Look for a non bbPress role
+		$roles = array_intersect(
+			array_values( $user->roles ),
+			array_keys( bbp_get_blog_roles() )
+		);
+
+		// If there's a role in the array, use the first one. This isn't very
+		// smart, but since roles aren't exactly hierarchical, and WordPress
+		// does not yet have a UI for multiple user roles, it's fine for now.
+		if ( ! empty( $roles ) ) {
+			$role = array_shift( $roles );
+		}
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_blog_role', $role, $user_id, $user );
+}
+
+/**
+ * Helper function hooked to 'bbp_profile_update' action to save or
+ * update user roles and capabilities.
+ *
+ * @since 2.2.0 bbPress (r4235)
+ *
+ * @param int $user_id
+ */
+function bbp_profile_update_role( $user_id = 0 ) {
+
+	// Bail if no user ID was passed
+	if ( empty( $user_id ) ) {
+		return;
+	}
+
+	// Bail if no role
+	if ( ! isset( $_POST['bbp-forums-role'] ) ) {
+		return;
+	}
+
+	// Forums role we want the user to have
+	$new_role    = sanitize_key( $_POST['bbp-forums-role'] );
+	$forums_role = bbp_get_user_role( $user_id );
+
+	// Bail if no role change
+	if ( $new_role === $forums_role ) {
+		return;
+	}
+
+	// Bail if trying to set their own role
+	if ( bbp_is_user_home_edit() ) {
+		return;
+	}
+
+	// Bail if current user cannot promote the passing user
+	if ( ! current_user_can( 'promote_user', $user_id ) ) {
+		return;
+	}
+
+	// Set the new forums role
+	bbp_set_user_role( $user_id, $new_role );
+}
+
+/**
+ * Check if a role string is valid
+ *
+ * @since 2.6.5
+ *
+ * @param string $role
+ *
+ * @return bool True if role is valid. False if role is not valid.
+ */
+function bbp_is_valid_role( $role = '' ) {
+
+	// Default return value
+	$retval = false;
+
+	// Skip if no role to check
+	if ( ! empty( $role ) && is_string( $role ) ) {
+
+		// Get the dynamic role IDs
+		$roles = array_keys( bbp_get_dynamic_roles() );
+
+		// Skip if no known role IDs
+		if ( ! empty( $roles ) ) {
+
+			// Is role in dynamic roles array?
+			$retval = in_array( $role, $roles, true );
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_valid_role', $retval, $role );
+}
+
+/**
+ * Add the default role to the current user if needed
+ *
+ * This function will bail if the forum is not global in a multisite
+ * installation of WordPress, or if the user is marked as spam or deleted.
+ *
+ * @since 2.0.0 bbPress (r3380)
+ *
+ * @return If not multisite, not global, or user is deleted/spammed
+ */
+function bbp_set_current_user_default_role() {
+
+	/** Sanity ****************************************************************/
+
+	// Bail if deactivating bbPress
+	if ( bbp_is_deactivation() ) {
+		return;
+	}
+
+	// Catch all, to prevent premature user initialization
+	if ( ! did_action( 'set_current_user' ) ) {
+		return;
+	}
+
+	// Bail if not logged in or already a member of this site
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	// Get the current user ID
+	$user_id = bbp_get_current_user_id();
+
+	// Bail if user already has a forums role
+	if ( bbp_get_user_role( $user_id ) ) {
+		return;
+	}
+
+	// Bail if user is marked as spam or is deleted
+	if ( bbp_is_user_inactive( $user_id ) ) {
+		return;
+	}
+
+	/** Ready *****************************************************************/
+
+	// Load up bbPress once
+	$bbp         = bbpress();
+
+	// Get whether or not to add a role to the user account
+	$add_to_site = bbp_allow_global_access();
+
+	// Get the current user's WordPress role. Set to empty string if none found.
+	$user_role   = bbp_get_user_blog_role( $user_id );
+
+	// Get the role map
+	$role_map    = bbp_get_user_role_map();
+
+	/** Forum Role ************************************************************/
+
+	// Use a mapped role or default role
+	$new_role = empty( $user_role ) || ! isset( $role_map[ $user_role ] )
+		? bbp_get_default_role()
+		: $role_map[ $user_role ];
+
+	/** Add or Map ************************************************************/
+
+	// Add the user to the site
+	if ( true === $add_to_site ) {
+		bbp_set_user_role( $user_id, $new_role );
+
+	// Don't add the user, but still give them the correct caps dynamically
+	} else {
+		$bbp->current_user->caps[ $new_role ] = true;
+		$bbp->current_user->get_role_caps();
+	}
+}
+
+/**
+ * Return a map of WordPress roles to bbPress roles. Used to automatically grant
+ * appropriate bbPress roles to WordPress users that wouldn't already have a
+ * role in the forums. Also guarantees WordPress admins get the Keymaster role.
+ *
+ * @since 2.2.0 bbPress (r4334)
+ *
+ * @return array Filtered array of WordPress roles to bbPress roles
+ */
+function bbp_get_user_role_map() {
+
+	// Get the default role once here
+	$default_role = bbp_get_default_role();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_user_role_map', array(
+		'administrator' => bbp_get_keymaster_role(),
+		'editor'        => $default_role,
+		'author'        => $default_role,
+		'contributor'   => $default_role,
+		'subscriber'    => $default_role
+	) );
+}
+
+/** User Status ***************************************************************/
+
+/**
+ * Checks if the user has been marked as a spammer.
+ *
+ * @since 2.0.0 bbPress (r3355)
+ *
+ * @param int $user_id int The ID for the user.
+ * @return bool True if spammer, False if not.
+ */
+function bbp_is_user_spammer( $user_id = 0 ) {
+
+	// Default to current user
+	if ( empty( $user_id ) && is_user_logged_in() ) {
+		$user_id = bbp_get_current_user_id();
+	}
+
+	// No user to check
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Assume user is not spam
+	$is_spammer = false;
+
+	// Get user data
+	$user = get_userdata( $user_id );
+
+	// No user found
+	if ( empty( $user ) ) {
+		$is_spammer = false;
+
+	// Check if spam
+	} elseif ( ! empty( $user->spam ) ) {
+		$is_spammer = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_core_is_user_spammer', $is_spammer );
+}
+
+/**
+ * Mark a users topics and replies as spam when the user is marked as spam
+ *
+ * @since 2.0.0 bbPress (r3405)
+ *
+ * @param int $user_id Optional. User ID to spam. Defaults to displayed user.
+ *
+ * @return bool If no user ID passed.
+ */
+function bbp_make_spam_user( $user_id = 0 ) {
+
+	// Use displayed user if it's not yourself
+	if ( empty( $user_id ) && bbp_is_single_user() && ! bbp_is_user_home() ) {
+		$user_id = bbp_get_displayed_user_id();
+	}
+
+	// Bail if no user ID
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Bail if user ID is keymaster
+	if ( bbp_is_user_keymaster( $user_id ) ) {
+		return false;
+	}
+
+	// Arm the torpedos
+	$bbp_db = bbp_db();
+
+	// Get the blog IDs of the user to mark as spam
+	$blogs = get_blogs_of_user( $user_id, true );
+
+	// If user has no blogs, they are a guest on this site
+	if ( empty( $blogs ) ) {
+		$blogs[ $bbp_db->blogid ] = array();
+	}
+
+	// Get array of post types to mark as spam
+	$post_types = array( bbp_get_topic_post_type(), bbp_get_reply_post_type() );
+	$post_types = "'" . implode( "', '", $post_types ) . "'";
+
+	// Get array of statuses to mark as spam
+	$post_statuses = bbp_get_public_topic_statuses();
+	$post_statuses = "'" . implode( "', '", $post_statuses ) . "'";
+
+	// Loop through blogs and remove their posts
+	foreach ( (array) array_keys( $blogs ) as $blog_id ) {
+
+		// Switch to the site ID
+		bbp_switch_to_site( $blog_id );
+
+		// Get topics and replies
+		$query = $bbp_db->prepare( "SELECT ID FROM {$bbp_db->posts} WHERE post_author = %d AND post_status IN ( {$post_statuses} ) AND post_type IN ( {$post_types} )", $user_id );
+		$posts = $bbp_db->get_col( $query );
+
+		// Loop through posts and spam them
+		if ( ! empty( $posts ) ) {
+			foreach ( $posts as $post_id ) {
+
+				// The routines for topics ang replies are different, so use the
+				// correct one based on the post type
+				switch ( get_post_type( $post_id ) ) {
+
+					case bbp_get_topic_post_type() :
+						bbp_spam_topic( $post_id );
+						break;
+
+					case bbp_get_reply_post_type() :
+						bbp_spam_reply( $post_id );
+						break;
+				}
+			}
+		}
+
+		// Switch back to current site
+		bbp_restore_current_site();
+	}
+
+	// Delete user options
+	bbp_delete_user_options( $user_id );
+
+	// Success
+	return true;
+}
+
+/**
+ * Mark a users topics and replies as spam when the user is marked as spam
+ *
+ * @since 2.0.0 bbPress (r3405)
+ *
+ * @param int $user_id Optional. User ID to unspam. Defaults to displayed user.
+ *
+ * @return bool If no user ID passed.
+ */
+function bbp_make_ham_user( $user_id = 0 ) {
+
+	// Use displayed user if it's not yourself
+	if ( empty( $user_id ) && bbp_is_single_user() && ! bbp_is_user_home() ) {
+		$user_id = bbp_get_displayed_user_id();
+	}
+
+	// Bail if no user ID
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Bail if user ID is keymaster
+	if ( bbp_is_user_keymaster( $user_id ) ) {
+		return false;
+	}
+
+	// Arm the torpedos
+	$bbp_db = bbp_db();
+
+	// Get the blog IDs of the user to mark as spam
+	$blogs = get_blogs_of_user( $user_id, true );
+
+	// If user has no blogs, they are a guest on this site
+	if ( empty( $blogs ) ) {
+		$blogs[ $bbp_db->blogid ] = array();
+	}
+
+	// Get array of post types to mark as spam
+	$post_types = array( bbp_get_topic_post_type(), bbp_get_reply_post_type() );
+	$post_types = "'" . implode( "', '", $post_types ) . "'";
+
+	// Get array of statuses to unmark as spam
+	$post_statuses = array( bbp_get_spam_status_id() );
+	$post_statuses = "'" . implode( "', '", $post_statuses ) . "'";
+
+	// Loop through blogs and remove their posts
+	foreach ( (array) array_keys( $blogs ) as $blog_id ) {
+
+		// Switch to the site ID
+		bbp_switch_to_site( $blog_id );
+
+		// Get topics and replies
+		$query = $bbp_db->prepare( "SELECT ID FROM {$bbp_db->posts} WHERE post_author = %d AND post_status IN ( {$post_statuses} ) AND post_type IN ( {$post_types} )", $user_id );
+		$posts = $bbp_db->get_col( $query );
+
+		// Loop through posts and spam them
+		if ( ! empty( $posts ) ) {
+			foreach ( $posts as $post_id ) {
+
+				// The routines for topics ang replies are different, so use the
+				// correct one based on the post type
+				switch ( get_post_type( $post_id ) ) {
+
+					case bbp_get_topic_post_type() :
+						bbp_unspam_topic( $post_id );
+						break;
+
+					case bbp_get_reply_post_type() :
+						bbp_unspam_reply( $post_id );
+						break;
+				}
+			}
+		}
+
+		// Switch back to current site
+		bbp_restore_current_site();
+	}
+
+	// Update topic & reply counts
+	bbp_update_user_topic_count( $user_id, bbp_get_user_topic_count_raw( $user_id ) );
+	bbp_update_user_reply_count( $user_id, bbp_get_user_reply_count_raw( $user_id ) );
+
+	// Update last posted (to now)
+	bbp_update_user_last_posted( $user_id );
+
+	// Success
+	return true;
+}
+
+/**
+ * Checks if the user has been marked as deleted.
+ *
+ * @since 2.0.0 bbPress (r3355)
+ *
+ * @param int $user_id int The ID for the user.
+ * @return bool True if deleted, False if not.
+ */
+function bbp_is_user_deleted( $user_id = 0 ) {
+
+	// Default to current user
+	if ( empty( $user_id ) && is_user_logged_in() ) {
+		$user_id = bbp_get_current_user_id();
+	}
+
+	// No user to check
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Assume user is not deleted
+	$is_deleted = false;
+
+	// Get user data
+	$user = get_userdata( $user_id );
+
+	// No user found
+	if ( empty( $user ) ) {
+		$is_deleted = true;
+
+	// Check if deleted
+	} elseif ( ! empty( $user->deleted ) ) {
+		$is_deleted = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_core_is_user_deleted', $is_deleted );
+}
+
+/**
+ * Checks if user is active
+ *
+ * @since 2.0.0 bbPress (r3502)
+ *
+ * @param int $user_id The user ID to check
+ * @return bool True if public, false if not
+ */
+function bbp_is_user_active( $user_id = 0 ) {
+
+	// No user to check
+	$user_id = bbp_get_user_id( $user_id, false, true );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Check spam
+	if ( bbp_is_user_spammer( $user_id ) ) {
+		return false;
+	}
+
+	// Check deleted
+	if ( bbp_is_user_deleted( $user_id ) ) {
+		return false;
+	}
+
+	// Assume true if not spam or deleted
+	return true;
+}
+
+/**
+ * Checks if user is not active.
+ *
+ * @since 2.0.0 bbPress (r3502)
+ *
+ * @param int $user_id The user ID to check. Defaults to current user ID
+ * @return bool True if inactive, false if active
+ */
+function bbp_is_user_inactive( $user_id = 0 ) {
+	return ! bbp_is_user_active( $user_id );
+}
+
+/**
+ * Checks if user is a keymaster
+ *
+ * @since 2.3.0 bbPress (r4783)
+ *
+ * @param int $user_id
+ * @return bool True if keymaster, false if not
+ */
+function bbp_is_user_keymaster( $user_id = 0 ) {
+	$_user_id = bbp_get_user_id( $user_id, false, true );
+	$retval   = user_can( $_user_id, 'keep_gate' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_keymaster', $retval, $_user_id, $user_id );
+}
+
+/**
+ * Does a user have a profile for the current site
+ *
+ * @since 2.2.0 bbPress (r4362)
+ *
+ * @param int $user_id User ID to check
+ *
+ * @return bool Whether or not the user has a profile on this blog_id.
+ */
+function bbp_user_has_profile( $user_id = 0 ) {
+
+	// Assume every user has a profile
+	$retval  = true;
+
+	// Validate user ID, default to displayed or current user
+	$user_id = bbp_get_user_id( $user_id, true, true );
+
+	// Try to get this user's data
+	$user    = get_userdata( $user_id );
+
+	// No user found, return false
+	if ( empty( $user ) ) {
+		$retval = false;
+
+	// User is inactive, and current user is not a keymaster
+	} elseif ( ! bbp_is_user_keymaster() && bbp_is_user_inactive( $user->ID ) ) {
+		$retval = false;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_show_user_profile', $retval, $user_id );
+}
+
+/** Moderators ****************************************************************/
+
+/**
+ * Add a moderator to an object
+ *
+ * @since 2.6.0 bbPress (r6056)
+ *
+ * @param int    $object_id   Traditionally a post ID
+ * @param int    $user_id     User ID
+ * @param string $object_type Type of meta (post,term,user,comment)
+ *
+ * @return bool
+ */
+function bbp_add_moderator( $object_id = 0, $user_id = 0, $object_type = 'post' ) {
+	return bbp_add_user_to_object( $object_id, $user_id, '_bbp_moderator_id', $object_type );
+}
+
+/**
+ * Remove a moderator user ID from an object
+ *
+ * @since 2.6.0 bbPress (r6056)
+ *
+ * @param int    $object_id   Traditionally a post ID
+ * @param int    $user_id     User ID
+ * @param string $object_type Type of meta (post,term,user,comment)
+ *
+ * @return bool
+ */
+function bbp_remove_moderator( $object_id = 0, $user_id = 0, $object_type = 'post' ) {
+	return bbp_remove_user_from_object( $object_id, $user_id, '_bbp_moderator_id', $object_type );
+}
+
+/**
+ * Get user IDs of moderators for an object
+ *
+ * @since 2.6.0 bbPress (r6056)
+ *
+ * @param int    $object_id   Traditionally a post ID
+ * @param string $object_type Type of meta (post,term,user,comment)
+ *
+ * @return array
+ */
+function bbp_get_moderator_ids( $object_id = 0, $object_type = 'post' ) {
+	return bbp_get_users_for_object( $object_id, '_bbp_moderator_id', $object_type );
+}
+
+/**
+ * Get moderators for a specific object ID. Will return global moderators when
+ * object ID is empty.
+ *
+ * @since 2.6.0 bbPress (r6056)
+ *
+ * @param int    $object_id   Traditionally a post ID
+ * @param string $object_type Type of meta (post,term,user,comment)
+ *
+ * @return array
+ */
+function bbp_get_moderators( $object_id = 0, $object_type = 'post' ) {
+
+	// Get global moderators
+	if ( empty( $object_id ) ) {
+		$users = get_users( array(
+			'role__in' => bbp_get_moderator_role(),
+		) );
+
+	// Get object moderators
+	} else {
+		$users = get_users( array(
+			'include' => bbp_get_moderator_ids( $object_id, $object_type ),
+		) );
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_moderators', $users, $object_id, $object_type );
+}
Index: /tags/2.6.6/src/includes/users/engagements.php
===================================================================
--- /tags/2.6.6/src/includes/users/engagements.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/engagements.php	(revision 7162)
@@ -0,0 +1,1229 @@
+<?php
+
+/**
+ * bbPress User Engagement Functions
+ *
+ * @package bbPress
+ * @subpackage Engagements
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** User Relationships ********************************************************/
+
+/**
+ * Add a user id to an object
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $object_id The object id
+ * @param int    $user_id   The user id
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ * @param bool   $unique    Whether meta key should be unique to the object
+ *
+ * @return bool Returns true on success, false on failure
+ */
+function bbp_add_user_to_object( $object_id = 0, $user_id = 0, $rel_key = '', $rel_type = 'post', $unique = false ) {
+	$object_id = absint( $object_id );
+	$user_id   = absint( $user_id );
+	$retval    = bbp_user_engagements_interface( $rel_key, $rel_type )->add_user_to_object( $object_id, $user_id, $rel_key, $rel_type, $unique );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_add_user_to_object', $retval, $object_id, $user_id, $rel_key, $rel_type, $unique );
+}
+
+/**
+ * Remove a user id from an object
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $object_id The object id
+ * @param int    $user_id   The user id
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ *
+ * @return bool Returns true on success, false on failure
+ */
+function bbp_remove_user_from_object( $object_id = 0, $user_id = 0, $rel_key = '', $rel_type = 'post' ) {
+	$retval = bbp_user_engagements_interface( $rel_key, $rel_type )->remove_user_from_object( $object_id, $user_id, $rel_key, $rel_type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_remove_user_from_object', $retval, $object_id, $user_id, $rel_key, $rel_type );
+}
+
+/**
+ * Remove a user id from all objects
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $user_id  The user id
+ * @param string $rel_key  The relationship key
+ * @param string $rel_type The relationship type (usually 'post')
+ *
+ * @return bool Returns true on success, false on failure
+ */
+function bbp_remove_user_from_all_objects( $user_id = 0, $rel_key = '', $rel_type = 'post' ) {
+	$user_id = absint( $user_id );
+	$retval  = bbp_user_engagements_interface( $rel_key, $rel_type )->remove_user_from_all_objects( $user_id, $rel_key, $rel_type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_remove_user_from_all_objects', $retval, $user_id, $rel_key, $rel_type );
+}
+
+/**
+ * Remove an object from all users
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $object_id The object id
+ * @param int    $user_id   The user id
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ *
+ * @return bool Returns true on success, false on failure
+ */
+function bbp_remove_object_from_all_users( $object_id = 0, $rel_key = '', $rel_type = 'post' ) {
+	$object_id = absint( $object_id );
+	$retval    = bbp_user_engagements_interface( $rel_key, $rel_type )->remove_object_from_all_users( $object_id, $rel_key, $rel_type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_remove_object_from_all_users', $retval, $object_id, $rel_key, $rel_type );
+}
+
+/**
+ * Remove all users from all objects
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param string $rel_key  The relationship key
+ * @param string $rel_type The relationship type (usually 'post')
+ *
+ * @return bool Returns true on success, false on failure
+ */
+function bbp_remove_all_users_from_all_objects( $rel_key = '', $rel_type = 'post' ) {
+	$retval = bbp_user_engagements_interface( $rel_key, $rel_type )->remove_all_users_from_all_objects( $rel_key, $rel_type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_remove_all_users_from_all_objects', $retval, $rel_key, $rel_type );
+}
+
+/**
+ * Get users of an object
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $object_id The object id
+ * @param string $rel_key   The key used to index this relationship
+ * @param string $rel_type  The type of meta to look in
+ *
+ * @return array Returns ids of users
+ */
+function bbp_get_users_for_object( $object_id = 0, $rel_key = '', $rel_type = 'post' ) {
+	$object_id = absint( $object_id );
+	$retval    = bbp_user_engagements_interface( $rel_key, $rel_type )->get_users_for_object( $object_id, $rel_key, $rel_type );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_users_for_object', $retval, $object_id, $rel_key, $rel_type );
+}
+
+/**
+ * Check if an object has a specific user
+ *
+ * @since 2.6.0 bbPress (r6109)
+ *
+ * @param int    $object_id The object id
+ * @param int    $user_id   The user id
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ *
+ * @return bool Returns true if object has a user, false if not
+ */
+function bbp_is_object_of_user( $object_id = 0, $user_id = 0, $rel_key = '', $rel_type = 'post' ) {
+	$object_id = absint( $object_id );
+	$user_id   = absint( $user_id );
+	$user_ids  = bbp_get_users_for_object( $object_id, $rel_key, $rel_type );
+	$retval    = is_numeric( array_search( $user_id, $user_ids, true ) );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_object_of_user', $retval, $object_id, $user_id, $rel_key, $rel_type );
+}
+
+/**
+ * Get the query part responsible for JOINing objects to user IDs
+ *
+ * @since 2.6.0 bbPress (r6747)
+ *
+ * @param array  $args      Default query arguments
+ * @param string $context   Additional context
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ *
+ * @return array
+ */
+function bbp_get_user_object_query( $args = array(), $context = '', $rel_key = '', $rel_type = 'post' ) {
+	$retval = bbp_user_engagements_interface( $rel_key, $rel_type )->get_query( $args, "get_user_{$context}", $rel_key, $rel_type );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_user_object_query', $retval, $args, $context, $rel_key, $rel_type );
+}
+
+/** Engagements ***************************************************************/
+
+/**
+ * Get the users who have engaged in a topic
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return array|bool Results if the topic has any engagements, otherwise false
+ */
+function bbp_get_topic_engagements( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$users    = bbp_get_users_for_object( $topic_id, '_bbp_engagement' );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_engagements', $users, $topic_id );
+}
+
+/**
+ * Return the users who have engaged in a topic, directly with a database query
+ *
+ * See: https://bbpress.trac.wordpress.org/ticket/3083
+ *
+ * @since 2.6.0 bbPress (r6522)
+ *
+ * @param int $topic_id
+ *
+ * @return array
+ */
+function bbp_get_topic_engagements_raw( $topic_id = 0 ) {
+
+	// Default variables
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$bbp_db   = bbp_db();
+	$statii   = "'" . implode( "', '", bbp_get_public_topic_statuses() ) . "'";
+
+	// A cool UNION query!
+	$sql = "
+SELECT DISTINCT( post_author ) FROM (
+	SELECT post_author FROM {$bbp_db->posts}
+		WHERE ( ID = %d AND post_status IN ({$statii}) AND post_type = %s )
+UNION
+	SELECT post_author FROM {$bbp_db->posts}
+		WHERE ( post_parent = %d AND post_status = %s AND post_type = %s )
+) as u1";
+
+	// Prepare & get results
+	$query   = $bbp_db->prepare( $sql, $topic_id, bbp_get_topic_post_type(), $topic_id, bbp_get_public_status_id(), bbp_get_reply_post_type() );
+	$results = $bbp_db->get_col( $query );
+
+	// Parse results into voices
+	$engagements = ! is_wp_error( $results )
+		? wp_parse_id_list( array_filter( $results ) )
+		: array();
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_engagements_raw', $engagements, $topic_id );
+}
+
+/**
+ * Get a user's topic engagements
+ *
+ * @since 2.6.0 bbPress (r6320)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args Optional. Arguments to pass into bbp_has_replies()
+ *
+ * @return bool True if user has engaged, otherwise false
+ */
+function bbp_get_user_engagements( $args = array() ) {
+	$r     = bbp_get_user_object_query( $args, 'engagements', '_bbp_engagement' );
+	$query = bbp_has_topics( $r );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_engagements', $query, 0, $r, $args );
+}
+
+/**
+ * Check if a user is engaged in a topic or not
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if the topic is in user's engagements, otherwise false
+ */
+function bbp_is_user_engaged( $user_id = 0, $topic_id = 0 ) {
+	$user_id  = bbp_get_user_id( $user_id, true, true );
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$retval   = bbp_is_object_of_user( $topic_id, $user_id, '_bbp_engagement' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_engaged', $retval, $user_id, $topic_id );
+}
+
+/**
+ * Add a topic to user's engagements
+ *
+ * Note that both the User and Topic should be verified to exist before using
+ * this function. Originally both were validated, but because this function is
+ * frequently used within a loop, those verifications were moved upstream to
+ * improve performance on topics with many engaged users.
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool Always true
+ */
+function bbp_add_user_engagement( $user_id = 0, $topic_id = 0 ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if already a engaged
+	if ( bbp_is_user_engaged( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if add fails
+	if ( ! bbp_add_user_to_object( $topic_id, $user_id, '_bbp_engagement' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_add_user_engagement', $user_id, $topic_id );
+
+	return true;
+}
+
+/**
+ * Remove a topic from user's engagements
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if the topic was removed from user's engagements, otherwise
+ *               false
+ */
+function bbp_remove_user_engagement( $user_id = 0, $topic_id = 0 ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if not already engaged
+	if ( ! bbp_is_user_engaged( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if remove fails
+	if ( ! bbp_remove_user_from_object( $topic_id, $user_id, '_bbp_engagement' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_remove_user_engagement', $user_id, $topic_id );
+
+	return true;
+}
+
+/**
+ * Recalculate all of the users who have engaged in a topic.
+ *
+ * This happens when permanently deleting a reply, because that reply author may
+ * have authored other replies to that same topic, or the topic itself.
+ *
+ * You may need to do this manually on heavily active forums where engagement
+ * count accuracy is important.
+ *
+ * @since 2.6.0 bbPress (r6522)
+ *
+ * @param int  $topic_id
+ * @param bool $force
+ *
+ * @return boolean True if any engagements are added, false otherwise
+ */
+function bbp_recalculate_topic_engagements( $topic_id = 0, $force = false ) {
+
+	// Default return value
+	$retval = false;
+
+	// Check post type
+	$topic_id = bbp_is_reply( $topic_id )
+		? bbp_get_reply_topic_id( $topic_id )
+		: bbp_get_topic_id( $topic_id );
+
+	// Bail if no topic ID
+	if ( empty( $topic_id ) ) {
+		return $retval;
+	}
+
+	// Query for engagements
+	$old_engagements = bbp_get_topic_engagements( $topic_id );
+	$new_engagements = bbp_get_topic_engagements_raw( $topic_id );
+
+	// Sort arrays
+	sort( $old_engagements, SORT_NUMERIC );
+	sort( $new_engagements, SORT_NUMERIC );
+
+	// Only recalculate on change
+	if ( ( true === $force ) || ( $old_engagements !== $new_engagements ) ) {
+
+		// Delete all engagements
+		bbp_remove_object_from_all_users( $topic_id, '_bbp_engagement' );
+
+		// Update the voice count for this topic id
+		foreach ( $new_engagements as $user_id ) {
+			$retval = bbp_add_user_engagement( $user_id, $topic_id );
+		}
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_recalculate_user_engagements', $retval, $topic_id );
+}
+
+/**
+ * Update the engagements of a topic.
+ *
+ * Hooked to 'bbp_new_topic' and 'bbp_new_reply', this gets the post author and
+ * if not anonymous, passes it into bbp_add_user_engagement().
+ *
+ * @since 2.6.0 bbPress (r6526)
+ *
+ * @param int $topic_id
+ */
+function bbp_update_topic_engagements( $topic_id = 0 ) {
+
+	// Is a reply
+	if ( bbp_is_reply( $topic_id ) ) {
+
+		// Bail if reply isn't published
+		if ( ! bbp_is_reply_published( $topic_id ) ) {
+			return;
+		}
+
+		$author_id = bbp_get_reply_author_id( $topic_id );
+		$topic_id  = bbp_get_reply_topic_id( $topic_id );
+
+	// Is a topic
+	} elseif ( bbp_is_topic( $topic_id ) ) {
+
+		// Bail if topic isn't published
+		if ( ! bbp_is_topic_published( $topic_id ) ) {
+			return;
+		}
+
+		$author_id = bbp_get_topic_author_id( $topic_id );
+		$topic_id  = bbp_get_topic_id( $topic_id );
+
+	// Is unknown
+	} else {
+		return;
+	}
+
+	// Bail if topic is not public
+	if ( ! bbp_is_topic_public( $topic_id ) ) {
+		return;
+	}
+
+	// Return whether engagement was added
+	return bbp_add_user_engagement( $author_id, $topic_id );
+}
+
+/** Favorites *****************************************************************/
+
+/**
+ * Get the users who have made the topic favorite
+ *
+ * @since 2.0.0 bbPress (r2658)
+ *
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return array|bool Results if the topic has any favoriters, otherwise false
+ */
+function bbp_get_topic_favoriters( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$users    = bbp_get_users_for_object( $topic_id, '_bbp_favorite' );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_favoriters', $users, $topic_id );
+}
+
+/**
+ * Get a user's favorite topics
+ *
+ * @since 2.0.0 bbPress (r2652)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args Optional. Arguments to pass into bbp_has_topics()
+ *
+ * @return array Array of topics if user has favorites, otherwise empty array
+ */
+function bbp_get_user_favorites( $args = array() ) {
+	$r     = bbp_get_user_object_query( $args, 'favorites', '_bbp_favorite' );
+	$query = ! empty( $r )
+		? bbp_has_topics( $r )
+		: array();
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_favorites', $query, 0, $r, $args );
+}
+
+/**
+ * Check if a topic is in user's favorites or not
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if the topic is in user's favorites, otherwise false
+ */
+function bbp_is_user_favorite( $user_id = 0, $topic_id = 0 ) {
+	$retval = bbp_is_object_of_user( $topic_id, $user_id, '_bbp_favorite' );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_favorite', $retval, $user_id, $topic_id );
+}
+
+/**
+ * Add a topic to user's favorites
+ *
+ * Note that both the User and Topic should be verified to exist before using
+ * this function. Originally both were validated, but because this function is
+ * frequently used within a loop, those verifications were moved upstream to
+ * improve performance on topics with many engaged users.
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if the topic was added to user's favorites, otherwise false
+ */
+function bbp_add_user_favorite( $user_id = 0, $topic_id = 0 ) {
+	$user_id  = bbp_get_user_id( $user_id, false, false );
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if already a favorite
+	if ( bbp_is_user_favorite( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if add fails
+	if ( ! bbp_add_user_to_object( $topic_id, $user_id, '_bbp_favorite' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_add_user_favorite', $user_id, $topic_id );
+
+	return true;
+}
+
+/**
+ * Remove a topic from user's favorites
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return bool True if the topic was removed from user's favorites, otherwise false
+ */
+function bbp_remove_user_favorite( $user_id, $topic_id ) {
+	$user_id  = bbp_get_user_id( $user_id, false, false );
+	$topic_id = bbp_get_topic_id( $topic_id );
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if not already a favorite
+	if ( ! bbp_is_user_favorite( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if remove fails
+	if ( ! bbp_remove_user_from_object( $topic_id, $user_id, '_bbp_favorite' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_remove_user_favorite', $user_id, $topic_id );
+
+	return true;
+}
+
+/**
+ * Handles the front end adding and removing of favorite topics
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_favorites_handler( $action = '' ) {
+
+	// Default
+	$success = false;
+
+	// Bail if favorites not active
+	if ( ! bbp_is_favorites_active() ) {
+		return $success;
+	}
+
+	// Bail if no topic ID is passed
+	if ( empty( $_GET['object_id'] ) ) {
+		return $success;
+	}
+
+	// Setup possible get actions
+	$possible_actions = array(
+		'bbp_favorite_add',
+		'bbp_favorite_remove',
+	);
+
+	// Bail if actions aren't meant for this function
+	if ( ! in_array( $action, $possible_actions, true ) ) {
+		return $success;
+	}
+
+	// What action is taking place?
+	$topic_id = bbp_get_topic_id( $_GET['object_id'] );
+	$user_id  = bbp_get_user_id( 0, true, true );
+
+	// Check for empty topic
+	if ( empty( $topic_id ) ) {
+		bbp_add_error( 'bbp_favorite_topic_id', __( '<strong>Error</strong>: No topic was found. Which topic are you marking/unmarking as favorite?', 'bbpress' ) );
+
+	// Check nonce
+	} elseif ( ! bbp_verify_nonce_request( 'toggle-favorite_' . $topic_id ) ) {
+		bbp_add_error( 'bbp_favorite_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+
+	// Check current user's ability to edit the user
+	} elseif ( ! current_user_can( 'edit_user', $user_id ) ) {
+		bbp_add_error( 'bbp_favorite_permission', __( '<strong>Error</strong>: You do not have permission to edit favorites for that user.', 'bbpress' ) );
+	}
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return $success;
+	}
+
+	/** No errors *************************************************************/
+
+	if ( 'bbp_favorite_remove' === $action ) {
+		$success = bbp_remove_user_favorite( $user_id, $topic_id );
+	} elseif ( 'bbp_favorite_add' === $action ) {
+		$success = bbp_add_user_favorite( $user_id, $topic_id );
+	}
+
+	// Do additional favorites actions
+	do_action( 'bbp_favorites_handler', $success, $user_id, $topic_id, $action );
+
+	// Success!
+	if ( true === $success ) {
+
+		// Redirect back from whence we came
+		if ( ! empty( $_REQUEST['redirect_to'] ) ) {
+			$redirect = $_REQUEST['redirect_to']; // Validated later
+		} elseif ( bbp_is_favorites() ) {
+			$redirect = bbp_get_favorites_permalink( $user_id, true );
+		} elseif ( bbp_is_single_user() ) {
+			$redirect = bbp_get_user_profile_url();
+		} elseif ( is_singular( bbp_get_topic_post_type() ) ) {
+			$redirect = bbp_get_topic_permalink( $topic_id );
+		} elseif ( is_single() || is_page() ) {
+			$redirect = get_permalink();
+		} else {
+			$redirect = get_permalink( $topic_id );
+		}
+
+		bbp_redirect( $redirect );
+
+	// Fail! Handle errors
+	} elseif ( 'bbp_favorite_remove' === $action ) {
+		bbp_add_error( 'bbp_favorite_remove', __( '<strong>Error</strong>: There was a problem removing that topic from favorites.', 'bbpress' ) );
+	} elseif ( 'bbp_favorite_add' === $action ) {
+		bbp_add_error( 'bbp_favorite_add',    __( '<strong>Error</strong>: There was a problem favoriting that topic.', 'bbpress' ) );
+	}
+
+	return (bool) $success;
+}
+
+/** Subscriptions *************************************************************/
+
+/**
+ * Get the users who have subscribed
+ *
+ * @since 2.6.0 bbPress (r5156)
+ *
+ * @param int $object_id Optional. ID of object (forum, topic, or something else)
+ */
+function bbp_get_subscribers( $object_id = 0, $type = 'post' ) {
+	$users = bbp_get_users_for_object( $object_id, '_bbp_subscription', $type );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_subscribers', $users, $object_id, $type );
+}
+
+/**
+ * Get a user's subscribed topics
+ *
+ * @since 2.0.0 bbPress (r2668)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args Optional. Arguments to pass into bbp_has_topics()
+ *
+ * @return array Array of topics if user has topic subscriptions, otherwise empty array
+ */
+function bbp_get_user_topic_subscriptions( $args = array() ) {
+	$r     = bbp_get_user_object_query( $args, 'topic_subscriptions', '_bbp_subscription' );
+	$query = ! empty( $r )
+		? bbp_has_topics( $r )
+		: array();
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_topic_subscriptions', $query, 0, $r, $args );
+}
+
+/**
+ * Get a user's subscribed forums
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args Optional. Arguments to pass into bbp_has_forums()
+ *
+ * @return array Array of forums if user has forum subscriptions, otherwise empty array
+ */
+function bbp_get_user_forum_subscriptions( $args = array() ) {
+	$r     = bbp_get_user_object_query( $args, 'forum_subscriptions', '_bbp_subscription' );
+	$query = ! empty( $r )
+		? bbp_has_forums( $r )
+		: array();
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_forum_subscriptions', $query, 0, $r, $args );
+}
+
+/**
+ * Check if an object (forum or topic) is in user's subscription list or not
+ *
+ * @since 2.5.0 bbPress (r5156)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $object_id Optional. Object id
+ *
+ * @return bool True if the object (forum or topic) is in user's subscriptions, otherwise false
+ */
+function bbp_is_user_subscribed( $user_id = 0, $object_id = 0, $type = 'post' ) {
+	$retval = bbp_is_object_of_user( $object_id, $user_id, '_bbp_subscription', $type );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_user_subscribed', $retval, $user_id, $object_id, $type );
+}
+
+/**
+ * Add a user subscription
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6544) Added $type parameter
+ *
+ * @param int    $user_id   Optional. User id
+ * @param int    $object_id Optional. Object id
+ * @param string $type      Optional. Type of object being subscribed to
+ *
+ * @return bool True if the object was added to user subscriptions, otherwise false
+ */
+function bbp_add_user_subscription( $user_id = 0, $object_id = 0, $type = 'post' ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $object_id ) ) {
+		return false;
+	}
+
+	// Bail if already subscribed
+	if ( bbp_is_user_subscribed( $user_id, $object_id, $type ) ) {
+		return false;
+	}
+
+	// Bail if add fails
+	if ( ! bbp_add_user_to_object( $object_id, $user_id, '_bbp_subscription', $type ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_add_user_subscription', $user_id, $object_id, $type );
+
+	return true;
+}
+
+/**
+ * Remove a user subscription
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @since 2.6.0 bbPress (r6544) Added $type parameter
+ *
+ * @param int    $user_id   Optional. User id
+ * @param int    $object_id Optional. Object id
+ * @param string $type      Optional. Type of object being subscribed to
+ *
+ * @return bool True if the object was removed from user subscriptions, otherwise false
+ */
+function bbp_remove_user_subscription( $user_id = 0, $object_id = 0, $type = 'post' ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $object_id ) ) {
+		return false;
+	}
+
+	// Bail if not subscribed
+	if ( ! bbp_is_user_subscribed( $user_id, $object_id, $type ) ) {
+		return false;
+	}
+
+	// Bail if remove fails
+	if ( ! bbp_remove_user_from_object( $object_id, $user_id, '_bbp_subscription', $type ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_remove_user_subscription', $user_id, $object_id, $type );
+
+	return true;
+}
+
+/**
+ * Handles the front end toggling of user subscriptions
+ *
+ * @since 2.0.0 bbPress (r2790)
+ * @since 2.6.l bbPress (r6543)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_subscriptions_handler( $action = '' ) {
+
+	// Default
+	$success = false;
+
+	// Bail if subscriptions not active
+	if ( ! bbp_is_subscriptions_active() ) {
+		return $success;
+	}
+
+	// Bail if no object ID is passed
+	if ( empty( $_GET['object_id'] ) ) {
+		return $success;
+	}
+
+	// Setup possible get actions
+	$possible_actions = array(
+		'bbp_subscribe',
+		'bbp_unsubscribe'
+	);
+
+	// Bail if actions aren't meant for this function
+	if ( ! in_array( $action, $possible_actions, true ) ) {
+		return $success;
+	}
+
+	// Get required data
+	$user_id     = bbp_get_current_user_id();
+	$object_id   = absint( $_GET['object_id'] );
+	$object_type = ! empty( $_GET['object_type'] )
+		? sanitize_key( $_GET['object_type'] )
+		: 'post';
+
+	// Check for empty topic
+	if ( empty( $object_id ) ) {
+		bbp_add_error( 'bbp_subscription_object_id', __( '<strong>Error</strong>: Not found. What are you subscribing/unsubscribing to?', 'bbpress' ) );
+
+	// Check nonce
+	} elseif ( ! bbp_verify_nonce_request( 'toggle-subscription_' . $object_id ) ) {
+		bbp_add_error( 'bbp_subscription_object_id', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+
+	// Check current user's ability to edit the user
+	} elseif ( ! current_user_can( 'edit_user', $user_id ) ) {
+		bbp_add_error( 'bbp_subscription_permission', __( '<strong>Error</strong>: You do not have permission to edit subscriptions of that user.', 'bbpress' ) );
+	}
+
+	// Bail if we have errors
+	if ( bbp_has_errors() ) {
+		return $success;
+	}
+
+	/** No errors *************************************************************/
+
+	if ( 'bbp_unsubscribe' === $action ) {
+		$success = bbp_remove_user_subscription( $user_id, $object_id, $object_type );
+	} elseif ( 'bbp_subscribe' === $action ) {
+		$success = bbp_add_user_subscription( $user_id, $object_id, $object_type );
+	}
+
+	// Do additional subscriptions actions
+	do_action( 'bbp_subscriptions_handler', $success, $user_id, $object_id, $action, $object_type );
+
+	// Success!
+	if ( true === $success ) {
+
+		// Redirect back from whence we came
+		if ( ! empty( $_REQUEST['redirect_to'] ) ) {
+			$redirect = $_REQUEST['redirect_to']; // Validated later
+		} elseif ( bbp_is_subscriptions() ) {
+			$redirect = bbp_get_subscriptions_permalink( $user_id );
+		} elseif ( bbp_is_single_user() ) {
+			$redirect = bbp_get_user_profile_url();
+		} elseif ( is_singular( bbp_get_topic_post_type() ) ) {
+			$redirect = bbp_get_topic_permalink( $object_id );
+		} elseif ( is_singular( bbp_get_forum_post_type() ) ) {
+			$redirect = bbp_get_forum_permalink( $object_id );
+		} elseif ( is_single() || is_page() ) {
+			$redirect = get_permalink();
+		} else {
+			$redirect = get_permalink( $object_id );
+		}
+
+		bbp_redirect( $redirect );
+
+	// Fail! Handle errors
+	} elseif ( 'bbp_unsubscribe' === $action ) {
+		bbp_add_error( 'bbp_unsubscribe', __( '<strong>Error</strong>: There was a problem unsubscribing.', 'bbpress' ) );
+	} elseif ( 'bbp_subscribe' === $action ) {
+		bbp_add_error( 'bbp_subscribe',   __( '<strong>Error</strong>: There was a problem subscribing.', 'bbpress' ) );
+	}
+
+	return (bool) $success;
+}
+
+/** Query Helpers *************************************************************/
+
+/**
+ * These functions are no longer used in bbPress due to general performance
+ * concerns on large installations. They are provided here for convenience and
+ * backwards compatibility only.
+ */
+
+/**
+ * Get a user's object IDs
+ *
+ * For the most part, you should not need to use this function, and may even
+ * want to come up with a more efficient way to get IDs on your own. Nevertheless,
+ * it is available here for your convenience, using the most efficient query
+ * parameters available inside of the various query APIs.
+ *
+ * @since 2.6.0 bbPress (r6606)
+ *
+ * @param int    $user_id   The user id
+ * @param string $rel_key   The relationship key
+ * @param string $rel_type  The relationship type (usually 'post')
+ * @param array  $args      The arguments to override defaults
+ *
+ * @return array|bool Results if user has objects, otherwise null
+ */
+function bbp_get_user_object_ids( $args = array() ) {
+	$object_ids = $defaults = array();
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, array(
+		'user_id'     => 0,
+		'object_type' => bbp_get_topic_post_type(),
+		'rel_key'     => '',
+		'rel_type'    => 'post',
+		'filter'      => 'user_object_ids',
+		'args'        => array()
+	), 'get_user_object_ids' );
+
+	// Sanitize arguments
+	$r['user_id']     = bbp_get_user_id( $r['user_id'] );
+	$r['rel_key']     = sanitize_key( $r['rel_key'] );
+	$r['rel_type']    = sanitize_key( $r['rel_type'] );
+	$r['object_type'] = sanitize_key( $r['object_type'] );
+	$r['filter']      = sanitize_key( $r['filter'] );
+
+	// Defaults
+	if ( 'post' === $r['rel_type'] ) {
+		$defaults = array(
+			'fields'         => 'ids',
+			'post_type'      => $r['object_type'],
+			'posts_per_page' => -1,
+			'meta_query'     => array( array(
+				'key'     => $r['rel_key'],
+				'value'   => $r['user_id'],
+				'compare' => 'NUMERIC'
+			),
+
+			// Performance
+			'nopaging'               => true,
+			'suppress_filters'       => true,
+			'update_post_term_cache' => false,
+			'update_post_meta_cache' => false,
+			'ignore_sticky_posts'    => true,
+			'no_found_rows'          => true
+		) );
+	}
+
+	// Parse arguments
+	$query_args = bbp_parse_args( $r['args'], $defaults, "get_{$r['filter']}_args" );
+
+	// Queries
+	if ( 'post' === $r['rel_type'] ) {
+		$query      = new WP_Query( $query_args );
+		$object_ids = $query->posts;
+	}
+
+	// Filter & return
+	return (array) apply_filters( "bbp_get_{$r['filter']}", $object_ids, $r, $args );
+}
+
+/**
+ * Get array of forum IDs that a user can moderate
+ *
+ * @since 2.6.0 bbPress (r5834)
+ *
+ * @param int $user_id User id.
+ *
+ * @return array Return array of forum ids, or empty array
+ */
+function bbp_get_moderator_forum_ids( $user_id = 0 ) {
+	return bbp_get_user_object_ids( array(
+		'user_id'     => $user_id,
+		'rel_key'     => '_bbp_moderator_id',
+		'object_type' => bbp_get_forum_post_type(),
+		'filter'      => 'moderator_forum_ids'
+	) );
+}
+
+/**
+ * Get a user's engaged topic ids
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $user_id Optional. User id
+ *
+ * @return array Return array of topic ids, or empty array
+ */
+function bbp_get_user_engaged_topic_ids( $user_id = 0 ) {
+	return bbp_get_user_object_ids( array(
+		'user_id' => $user_id,
+		'rel_key' => '_bbp_engagement',
+		'filter'  => 'user_engaged_topic_ids'
+	) );
+}
+
+/**
+ * Get a user's favorite topic ids
+ *
+ * @since 2.0.0 bbPress (r2652)
+ *
+ * @param int $user_id Optional. User id
+ *
+ * @return array Return array of favorite topic ids, or empty array
+ */
+function bbp_get_user_favorites_topic_ids( $user_id = 0 ) {
+	return bbp_get_user_object_ids( array(
+		'user_id' => $user_id,
+		'rel_key' => '_bbp_favorite',
+		'filter'  => 'user_favorites_topic_ids'
+	) );
+}
+
+/**
+ * Get a user's subscribed forum ids
+ *
+ * @since 2.5.0 bbPress (r5156)
+ *
+ * @param int $user_id Optional. User id
+ *
+ * @return array Return array of subscribed forum ids, or empty array
+ */
+function bbp_get_user_subscribed_forum_ids( $user_id = 0 ) {
+	return bbp_get_user_object_ids( array(
+		'user_id'     => $user_id,
+		'rel_key'     => '_bbp_subscription',
+		'object_type' => bbp_get_forum_post_type(),
+		'filter'      => 'user_subscribed_forum_ids'
+	) );
+}
+
+/**
+ * Get a user's subscribed topic ids
+ *
+ * @since 2.0.0 bbPress (r2668)
+ *
+ * @param int $user_id Optional. User id
+ *
+ * @return array Return array of subscribed topic ids, or empty array
+ */
+function bbp_get_user_subscribed_topic_ids( $user_id = 0 ) {
+	return bbp_get_user_object_ids( array(
+		'user_id' => $user_id,
+		'rel_key' => '_bbp_subscription',
+		'filter'  => 'user_subscribed_topic_ids'
+	) );
+}
+
+/** Deprecated ****************************************************************/
+
+/**
+ * Get a user's subscribed topics
+ *
+ * @since 2.0.0 bbPress (r2668)
+ * @deprecated 2.5.0 bbPress (r5156)
+ *
+ * @param int $user_id Optional. User id
+ *
+ * @return array|bool Results if user has subscriptions, otherwise false
+ */
+function bbp_get_user_subscriptions( $user_id = 0 ) {
+	_deprecated_function( __FUNCTION__, '2.5', 'bbp_get_user_topic_subscriptions()' );
+	$query = bbp_get_user_topic_subscriptions( $user_id );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_subscriptions', $query, $user_id );
+}
+
+/**
+ * Get the users who have subscribed to the forum
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $forum_id Optional. forum id
+ *
+ * @return array|bool Results if the forum has any subscribers, otherwise false
+ */
+function bbp_get_forum_subscribers( $forum_id = 0 ) {
+	$users = bbp_get_users_for_object( $forum_id, '_bbp_subscription' );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_forum_subscribers', $users, $forum_id );
+}
+
+/**
+ * Get the users who have subscribed to the topic
+ *
+ * @since 2.0.0 bbPress (r2668)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $topic_id Optional. Topic id
+ *
+ * @return array|bool Results if the topic has any subscribers, otherwise false
+ */
+function bbp_get_topic_subscribers( $topic_id = 0 ) {
+	$users = bbp_get_users_for_object( $topic_id, '_bbp_subscription' );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_topic_subscribers', $users, $topic_id );
+}
+
+/**
+ * Check if a forum is in user's subscription list or not
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $user_id  Optional. User id
+ * @param int $forum_id Optional. Forum id
+ *
+ * @return bool True if the forum is in user's subscriptions, otherwise false
+ */
+function bbp_is_user_subscribed_to_forum( $user_id = 0, $forum_id = 0 ) {
+	return bbp_is_user_subscribed( $user_id, $forum_id );
+}
+
+/**
+ * Check if a topic is in user's subscription list or not
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543) Use bbp_is_user_subscribed()
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if the topic is in user's subscriptions, otherwise false
+ */
+function bbp_is_user_subscribed_to_topic( $user_id = 0, $topic_id = 0 ) {
+	return bbp_is_user_subscribed( $user_id, $topic_id );
+}
+
+/**
+ * Remove a forum from user's subscriptions
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $forum_id Optional. forum id
+ * @return bool True if the forum was removed from user's subscriptions,
+ *               otherwise false
+ */
+function bbp_remove_user_forum_subscription( $user_id = 0, $forum_id = 0 ) {
+	return bbp_remove_user_subscription( $user_id, $forum_id );
+}
+
+/**
+ * Remove a topic from user's subscriptions
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @return bool True if the topic was removed from user's subscriptions,
+ *               otherwise false
+ */
+function bbp_remove_user_topic_subscription( $user_id = 0, $topic_id = 0 ) {
+	return bbp_remove_user_subscription( $user_id, $topic_id );
+}
+
+/**
+ * Add a forum to user's subscriptions
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $forum_id Optional. forum id
+ * @return bool Always true
+ */
+function bbp_add_user_forum_subscription( $user_id = 0, $forum_id = 0 ) {
+	return bbp_add_user_subscription( $user_id, $forum_id );
+}
+
+/**
+ * Add a topic to user's subscriptions
+ *
+ * Note that both the User and Topic should be verified to exist before using
+ * this function. Originally both were validated, but because this function is
+ * frequently used within a loop, those verifications were moved upstream to
+ * improve performance on topics with many engaged users.
+ *
+ * @since 2.0.0 bbPress (r2668)
+ * @deprecated 2.6.0 bbPress (r6543)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @return bool Always true
+ */
+function bbp_add_user_topic_subscription( $user_id = 0, $topic_id = 0 ) {
+	return bbp_add_user_subscription( $user_id, $topic_id );
+}
+
+/**
+ * Handles the front end toggling of forum subscriptions
+ *
+ * @since 2.5.0 bbPress (r5156)
+ * @deprecated 2.6.0 bbPress (r6543)
+ */
+function bbp_forum_subscriptions_handler( $action = '' ) {
+	return bbp_subscriptions_handler( $action );
+}
Index: /tags/2.6.6/src/includes/users/functions.php
===================================================================
--- /tags/2.6.6/src/includes/users/functions.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/functions.php	(revision 7162)
@@ -0,0 +1,988 @@
+<?php
+
+/**
+ * bbPress User Functions
+ *
+ * @package bbPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Redirect back to $url when attempting to use the login page
+ *
+ * @since 2.0.0 bbPress (r2815)
+ *
+ * @param string $url The url
+ * @param string $raw_url Raw url
+ * @param object $user User object
+ */
+function bbp_redirect_login( $url = '', $raw_url = '', $user = '' ) {
+
+	// Raw redirect_to was passed, so use it
+	if ( ! empty( $raw_url ) ) {
+		$url = $raw_url;
+
+	// $url was manually set in wp-login.php to redirect to admin
+	} elseif ( admin_url() === $url ) {
+		$url = home_url();
+
+	// $url is empty
+	} elseif ( empty( $url ) ) {
+		$url = home_url();
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_redirect_login', $url, $raw_url, $user );
+}
+
+/**
+ * Is an anonymous topic/reply being made?
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @return bool True if anonymous is allowed and user is not logged in, false if
+ *               anonymous is not allowed or user is logged in
+ */
+function bbp_is_anonymous() {
+	$is_anonymous = ( ! is_user_logged_in() && bbp_allow_anonymous() );
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_is_anonymous', $is_anonymous );
+}
+
+/**
+ * Echoes the values for current poster (uses WP comment cookies)
+ *
+ * @since 2.0.0 bbPress (r2734)
+ *
+ * @param string $key Which value to echo?
+ */
+function bbp_current_anonymous_user_data( $key = '' ) {
+	echo esc_attr( bbp_get_current_anonymous_user_data( $key ) );
+}
+
+	/**
+	 * Get the cookies for current poster (uses WP comment cookies).
+	 *
+	 * @since 2.0.0 bbPress (r2734)
+	 *
+	 * @param string $key Optional. Which value to get? If not given, then
+	 *                     an array is returned.
+	 * @return string|array Cookie(s) for current poster
+	 */
+	function bbp_get_current_anonymous_user_data( $key = '' ) {
+
+		// Array of allowed cookie names
+		$cookie_names = array(
+			'name'  => 'comment_author',
+			'email' => 'comment_author_email',
+			'url'   => 'comment_author_url',
+
+			// Here just for the sake of them, use the above ones
+			'comment_author'       => 'comment_author',
+			'comment_author_email' => 'comment_author_email',
+			'comment_author_url'   => 'comment_author_url',
+		);
+
+		// Get the current poster's info from the cookies
+		$bbp_current_poster = wp_get_current_commenter();
+
+		// Sanitize the cookie key being retrieved
+		$key = sanitize_key( $key );
+
+		// Maybe return a specific key
+		if ( ! empty( $key ) && in_array( $key, array_keys( $cookie_names ), true ) ) {
+			return $bbp_current_poster[ $cookie_names[ $key ] ];
+		}
+
+		// Return all keys
+		return $bbp_current_poster;
+	}
+
+/**
+ * Set the cookies for current poster (uses WP comment cookies)
+ *
+ * @since 2.0.0 bbPress (r2734)
+ *
+ * @param array $anonymous_data Optional - if it's an anonymous post. Do not
+ *                              supply if supplying $author_id. Should be
+ *                              sanitized (see {@link bbp_filter_anonymous_post_data()}
+ */
+function bbp_set_current_anonymous_user_data( $anonymous_data = array() ) {
+
+	// Bail if empty or not an array
+	if ( empty( $anonymous_data ) || ! is_array( $anonymous_data ) ) {
+		return;
+	}
+
+	// Setup cookie expiration
+	$lifetime = (int) apply_filters( 'comment_cookie_lifetime', 30000000 );
+	$expiry   = time() + $lifetime;
+	$secure   = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) );
+
+	// Set the cookies
+	setcookie( 'comment_author_'       . COOKIEHASH, $anonymous_data['bbp_anonymous_name'],    $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
+	setcookie( 'comment_author_email_' . COOKIEHASH, $anonymous_data['bbp_anonymous_email'],   $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
+	setcookie( 'comment_author_url_'   . COOKIEHASH, $anonymous_data['bbp_anonymous_website'], $expiry, COOKIEPATH, COOKIE_DOMAIN, $secure );
+}
+
+/**
+ * Get the poster IP address
+ *
+ * @since 2.0.0 bbPress (r3120)
+ * @since 2.6.0 bbPress (r5609) Added `empty()` check for unit tests
+ *
+ * @return string
+ */
+function bbp_current_author_ip() {
+
+	// Check for remote address
+	$remote_address = ! empty( $_SERVER['REMOTE_ADDR'] )
+		? wp_unslash( $_SERVER['REMOTE_ADDR'] )
+		: '127.0.0.1';
+
+	// Remove any unsavory bits
+	$retval = preg_replace( '/[^0-9a-fA-F:., ]/', '', $remote_address );
+
+	// Filter & return
+	return apply_filters( 'bbp_current_author_ip', $retval, $remote_address );
+}
+
+/**
+ * Get the poster user agent
+ *
+ * @since 2.0.0 bbPress (r3446)
+ *
+ * @return string
+ */
+function bbp_current_author_ua() {
+	$retval = ! empty( $_SERVER['HTTP_USER_AGENT'] )
+		? mb_substr( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), 0, 254 )
+		: '';
+
+	// Filter & return
+	return apply_filters( 'bbp_current_author_ua', $retval );
+}
+
+/** Edit **********************************************************************/
+
+/**
+ * Handles the front end user editing from POST requests
+ *
+ * @since 2.0.0 bbPress (r2790)
+ *
+ * @param string $action The requested action to compare this function to
+ */
+function bbp_edit_user_handler( $action = '' ) {
+
+	// Bail if action is not `bbp-update-user`
+	if ( 'bbp-update-user' !== $action ) {
+		return;
+	}
+
+	// Bail if in wp-admin
+	if ( is_admin() ) {
+		return;
+	}
+
+	// Get the displayed user ID
+	$user_id = bbp_get_displayed_user_id();
+
+	// Nonce check
+	if ( ! bbp_verify_nonce_request( 'update-user_' . $user_id ) ) {
+		bbp_add_error( 'bbp_update_user_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Cap check
+	if ( ! current_user_can( 'edit_user', $user_id ) ) {
+		bbp_add_error( 'bbp_update_user_capability', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+		return;
+	}
+
+	// Empty email check
+	if ( empty( $_POST['email'] ) ) {
+		bbp_add_error( 'bbp_user_email_empty', __( '<strong>Error</strong>: That is not a valid email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
+		return;
+	}
+
+	// Get the users current email address to use for comparisons
+	$user_email = bbp_get_displayed_user_field( 'user_email', 'raw' );
+
+	// Bail if no email change
+	if ( $user_email !== $_POST['email'] ) {
+
+		// Check that new email address is valid
+		if ( ! is_email( $_POST['email'] ) ) {
+			bbp_add_error( 'bbp_user_email_invalid', __( '<strong>Error</strong>: That is not a valid email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
+			return;
+		}
+
+		// Check if email address is already in use
+		if ( email_exists( $_POST['email'] ) ) {
+			bbp_add_error( 'bbp_user_email_taken', __( '<strong>Error</strong>: That email address is already in use.', 'bbpress' ), array( 'form-field' => 'email' ) );
+			return;
+		}
+
+		// Update the option
+		$option = array(
+			'hash'     => md5( $_POST['email'] . time() . wp_rand() ),
+			'newemail' => $_POST['email'],
+		);
+		update_user_meta( $user_id, '_new_email', $option );
+
+		// Attempt to notify the user of email address change
+		bbp_edit_user_email_send_notification( $user_id, $option );
+
+		// Set the POST email variable back to the user's email address
+		// so `edit_user()` does not attempt to update it. This is not ideal,
+		// but it's also what send_confirmation_on_profile_email() does.
+		$_POST['email'] = $user_email;
+	}
+
+	// Do action based on who's profile you're editing
+	$edit_action = bbp_is_user_home_edit()
+		? 'personal_options_update'
+		: 'edit_user_profile_update';
+
+	do_action( $edit_action, $user_id );
+
+	// Prevent edit_user() from wiping out the user's Toolbar on front setting
+	if ( ! isset( $_POST['admin_bar_front'] ) && _get_admin_bar_pref( 'front', $user_id ) ) {
+		$_POST['admin_bar_front'] = 1;
+	}
+
+	// Bail if errors already exist
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	// Handle user edit
+	$edit_user = edit_user( $user_id );
+
+	// Error(s) editng the user, so copy them into the global
+	if ( is_wp_error( $edit_user ) ) {
+		bbpress()->errors = $edit_user;
+
+	// Successful edit to redirect
+	} elseif ( is_integer( $edit_user ) ) {
+
+		// Maybe update super admin ability
+		if ( is_multisite() && ! bbp_is_user_home_edit() && current_user_can( 'manage_network_options' ) && is_super_admin() ) {
+			empty( $_POST['super_admin'] )
+				? revoke_super_admin( $edit_user )
+				: grant_super_admin( $edit_user );
+		}
+
+		// Redirect
+		$args     = array( 'updated' => 'true' );
+		$user_url = bbp_get_user_profile_edit_url( $edit_user );
+		$redirect = add_query_arg( $args, $user_url );
+
+		bbp_redirect( $redirect );
+	}
+}
+
+/**
+ * Handles user email address updating from GET requests
+ *
+ * @since 2.6.0 bbPress (r5660)
+ *
+ * @param string $action
+ */
+function bbp_user_email_change_handler( $action = '' ) {
+
+	// Bail if action is not `bbp-update-user-email`
+	if ( 'bbp-update-user-email' !== $action ) {
+		return;
+	}
+
+	// Bail if not on users own profile
+	if ( ! bbp_is_user_home_edit() ) {
+		return;
+	}
+
+	// Bail if not attempting to modify user email address
+	if ( empty( $_GET['newuseremail'] ) && empty( $_GET['dismiss'] ) ) {
+		return;
+	}
+
+	// Get the displayed user ID & option key
+	$user_id     = bbp_get_displayed_user_id();
+	$key         = '_new_email';
+	$redirect_to = bbp_get_user_profile_edit_url( $user_id );
+
+	// Execute confirmed email change.
+	if ( ! empty( $_GET['newuseremail'] ) ) {
+
+		// Check for email address change option
+		$new_email = get_user_meta( $user_id, $key, true );
+
+		// Redirect if *no* email address change exists
+		if ( false === $new_email ) {
+			bbp_redirect( $redirect_to );
+		}
+
+		// Cleanup & redirect if *invalid* email address change exists
+		if ( empty( $new_email['hash'] ) || empty( $new_email['newemail'] ) ) {
+			delete_user_meta( $user_id, $key );
+
+			bbp_redirect( $redirect_to );
+		}
+
+		// Compare hashes, and update user if hashes match
+		if ( hash_equals( $new_email['hash'], $_GET['newuseremail'] ) ) {
+
+			// Does another user have this email address already?
+			if ( email_exists( $new_email['newemail'] ) ) {
+				delete_user_meta( $user_id, $key );
+
+				bbp_add_error( 'bbp_user_email_taken', __( '<strong>Error</strong>: That email address is already in use.', 'bbpress' ), array( 'form-field' => 'email' ) );
+
+			// Email address is good to change to
+			} else {
+
+				// Create a stdClass (for easy call to wp_update_user())
+				$user             = new stdClass();
+				$user->ID         = $user_id;
+				$user->user_email = esc_html( trim( $new_email['newemail'] ) );
+
+				// Attempt to update user email
+				$update_user = wp_update_user( $user );
+
+				// Error(s) editing the user, so copy them into the global
+				if ( is_wp_error( $update_user ) ) {
+					bbpress()->errors = $update_user;
+
+				// All done, so redirect and show the updated message
+				} else {
+
+					// Update signups table, if signups table & entry exists
+					// For Multisite & BuddyPress compatibility
+					$bbp_db = bbp_db();
+					if ( ! empty( $bbp_db->signups ) && $bbp_db->get_var( $bbp_db->prepare( "SELECT user_login FROM {$bbp_db->signups} WHERE user_login = %s", bbp_get_displayed_user_field( 'user_login', 'raw' ) ) ) ) {
+						$bbp_db->query( $bbp_db->prepare( "UPDATE {$bbp_db->signups} SET user_email = %s WHERE user_login = %s", $user->user_email, bbp_get_displayed_user_field( 'user_login', 'raw' ) ) );
+					}
+
+					delete_user_meta( $user_id, $key );
+
+					bbp_redirect( add_query_arg( array( 'updated' => 'true' ), $redirect_to ) );
+				}
+			}
+		}
+
+	// Delete new email address from user options
+	} elseif ( ! empty( $_GET['dismiss'] ) && ( "{$user_id}{$key}" === $_GET['dismiss'] ) ) {
+		if ( ! bbp_verify_nonce_request( "dismiss-{$user_id}{$key}" ) ) {
+			bbp_add_error( 'bbp_dismiss_new_email_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
+			return;
+		}
+
+		delete_user_meta( $user_id, $key );
+		bbp_redirect( $redirect_to );
+	}
+}
+
+/**
+ * Sends an email when an email address change occurs on POST requests
+ *
+ * @since 2.6.0 bbPress (r5660)
+ *
+ * @see send_confirmation_on_profile_email()
+ */
+function bbp_edit_user_email_send_notification( $user_id = 0, $args = array() ) {
+
+	// Parse args
+	$r = bbp_parse_args( $args, array(
+		'hash'     => '',
+		'newemail' => '',
+	) );
+
+	// Bail if any relevant parameters are empty
+	if ( empty( $user_id ) || empty( $r['hash'] ) || empty( $r['newemail'] ) ) {
+		bbp_add_error( 'bbp_user_email_invalid_hash', __( '<strong>Error</strong>: An error occurred while updating your email address.', 'bbpress' ), array( 'form-field' => 'email' ) );
+		return;
+	}
+
+	// Build the nonced URL to dismiss the pending change
+	$user_login  = bbp_get_displayed_user_field( 'user_login', 'raw' );
+	$user_url    = bbp_get_user_profile_edit_url( $user_id );
+	$confirm_url = add_query_arg( array(
+		'action'       => 'bbp-update-user-email',
+		'newuseremail' => $r['hash']
+	), $user_url );
+
+	$email_text = __( '%1$s
+
+Someone requested a change to the email address on your account.
+
+Please click the following link to confirm this change:
+%2$s
+
+If you did not request this, you can safely ignore and delete this notification.
+
+This email was sent to: %3$s
+
+Regards,
+The %4$s Team
+%5$s', 'bbpress' );
+
+	/**
+	 * Filter the email text sent when a user changes emails.
+	 *
+	 * The following strings have a special meaning and will get replaced dynamically:
+	 *
+	 * %1$s - The current user's username
+	 * %2$s - The link to click on to confirm the email change
+	 * %3$s - The new email
+	 * %4$s - The name of the site
+	 * %5$s - The URL to the site
+	 *
+	 * @param string $email_text Text in the email.
+	 * @param string $r          New user email that the current user has changed to.
+	 */
+	$content = apply_filters( 'bbp_user_email_update_content', $email_text, $r );
+
+	// Build the email message
+	$message = sprintf( $content, $user_login, $confirm_url, $r['newemail'], get_site_option( 'site_name' ), network_home_url() );
+
+	// Build the email subject
+	$subject = sprintf( __( '[%s] New Email Address', 'bbpress' ), wp_specialchars_decode( get_option( 'blogname' ) ) );
+
+	// Send the email
+	wp_mail( $r['newemail'], $subject, $message );
+}
+
+/**
+ * Conditionally hook the core WordPress output actions to the end of the
+ * default user's edit profile template
+ *
+ * This allows clever plugin authors to conditionally unhook the WordPress core
+ * output actions if they don't want any unexpected junk to appear there, and
+ * also avoids needing to pollute the templates with additional logic and actions.
+ *
+ * @since 2.2.0 bbPress (r4273)
+ */
+function bbp_user_edit_after() {
+	$action = bbp_is_user_home_edit() ? 'show_user_profile' : 'edit_user_profile';
+
+	do_action( $action, get_userdata( bbp_get_displayed_user_id() ) );
+}
+
+/** User Queries **************************************************************/
+
+/**
+ * Get the topics that a user created
+ *
+ * @since 2.0.0 bbPress (r2660)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args    Optional. Arguments to pass into bbp_has_topics()
+ *
+ * @return bool True if user has started topics, otherwise false
+ */
+function bbp_get_user_topics_started( $args = array() ) {
+
+	// Backwards compat for pre-2.6.0
+	if ( is_numeric( $args ) ) {
+		$args = array(
+			'author' => bbp_get_user_id( $args, false, false )
+		);
+	}
+
+	// Default arguments
+	$defaults = array(
+		'author' => bbp_get_displayed_user_id()
+	);
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, $defaults, 'get_user_topics_started' );
+
+	// Get the topics
+	$query   = bbp_has_topics( $r );
+	$user_id = $r['author'];
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_topics_started', $query, $user_id, $r, $args );
+}
+
+/**
+ * Get the replies that a user created
+ *
+ * @since 2.2.0 bbPress (r4225)
+ * @since 2.6.0 bbPress (r6618) Signature changed to accept an array of arguments
+ *
+ * @param array $args Optional. Arguments to pass into bbp_has_replies()
+ *
+ * @return bool True if user has created replies, otherwise false
+ */
+function bbp_get_user_replies_created( $args = array() ) {
+
+	// Backwards compat for pre-2.6.0
+	if ( is_numeric( $args ) ) {
+		$args = array(
+			'author' => bbp_get_user_id( $args, false, false ),
+			'post_type' => bbp_get_reply_post_type(),
+			'order'     => 'DESC'
+		);
+	}
+
+	// Default arguments
+	$defaults = array(
+		'author'    => bbp_get_displayed_user_id(),
+		'post_type' => bbp_get_reply_post_type(),
+		'order'     => 'DESC'
+	);
+
+	// Parse arguments
+	$r = bbp_parse_args( $args, $defaults, 'get_user_replies_created' );
+
+	// Get the replies
+	$query   = bbp_has_replies( $r );
+	$user_id = $r['author'];
+
+	// Filter & return
+	return apply_filters( 'bbp_get_user_replies_created', $query, $user_id, $r, $args );
+}
+
+/**
+ * Get user IDs from nicenames
+ *
+ * This function is primarily used when saving object moderators
+ *
+ * @since 2.6.0 bbPress
+ *
+ * @param mixed $user_nicenames
+ * @return array
+ */
+function bbp_get_user_ids_from_nicenames( $user_nicenames = array() ) {
+
+	// Default value
+	$retval = array();
+
+	// Only query if nicenames
+	if ( ! empty( $user_nicenames ) ) {
+
+		// Maybe explode by comma
+		$user_nicenames = ( is_string( $user_nicenames ) && strstr( $user_nicenames, ',' ) )
+			? explode( ',', $user_nicenames )
+			: (array) $user_nicenames;
+
+		// Sanitize each nicename in the array
+		$user_nicenames = array_map( 'sanitize_title', $user_nicenames );
+
+		// Get users
+		$users = get_users( array(
+			'nicename__in' => $user_nicenames
+		) );
+
+		// Pluck or empty
+		if ( ! empty( $users ) ) {
+			$retval = wp_list_pluck( $users, 'ID' );
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_user_ids_from_nicenames', $retval, $user_nicenames );
+}
+
+/**
+ * Get user nicenames from IDs
+ *
+ * This function is primarily used when saving object moderators
+ *
+ * @since 2.6.0 bbPress
+ *
+ * @param mixed $user_ids
+ * @return array
+ */
+function bbp_get_user_nicenames_from_ids( $user_ids = array() ) {
+
+	// Default value
+	$retval = array();
+
+	// Only query if nicenames
+	if ( ! empty( $user_ids ) ) {
+
+		// Get users
+		$users = get_users( array(
+			'include' => $user_ids
+		) );
+
+		// Pluck or empty
+		if ( ! empty( $users ) ) {
+			$retval = wp_list_pluck( $users, 'user_nicename' );
+		}
+	}
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_user_nicenames_from_ids', $retval, $user_ids );
+}
+
+/** Post Counts ***************************************************************/
+
+/**
+ * Return the raw database count of topics by a user
+ *
+ * @since 2.1.0 bbPress (r3633)
+ *
+ * @param int $user_id User ID to get count for
+ *
+ * @return int Raw DB count of topics
+ */
+function bbp_get_user_topic_count_raw( $user_id = 0 ) {
+	$user_id = bbp_get_user_id( $user_id );
+	$bbp_db  = bbp_db();
+	$statii  = "'" . implode( "', '", bbp_get_public_topic_statuses() ) . "'";
+	$sql     = "SELECT COUNT(*)
+			FROM {$bbp_db->posts}
+			WHERE post_author = %d
+				AND post_type = %s
+				AND post_status IN ({$statii})";
+
+	$query   = $bbp_db->prepare( $sql, $user_id, bbp_get_topic_post_type() );
+	$count   = (int) $bbp_db->get_var( $query );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_user_topic_count_raw', $count, $user_id );
+}
+
+/**
+ * Return the raw database count of replies by a user
+ *
+ * @since 2.1.0 bbPress (r3633)
+ *
+ * @param int $user_id User ID to get count for
+ *
+ * @return int Raw DB count of replies
+ */
+function bbp_get_user_reply_count_raw( $user_id = 0 ) {
+	$user_id = bbp_get_user_id( $user_id );
+	$bbp_db  = bbp_db();
+	$statii  = "'" . implode( "', '", bbp_get_public_reply_statuses() ) . "'";
+	$sql     = "SELECT COUNT(*)
+			FROM {$bbp_db->posts}
+			WHERE post_author = %d
+				AND post_type = %s
+				AND post_status IN ({$statii})";
+
+	$query   = $bbp_db->prepare( $sql, $user_id, bbp_get_reply_post_type() );
+	$count   = (int) $bbp_db->get_var( $query );
+
+	// Filter & return
+	return (int) apply_filters( 'bbp_get_user_reply_count_raw', $count, $user_id );
+}
+
+/**
+ * Bump the topic count for a user by a certain amount.
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param int $user_id
+ * @param int $difference
+ */
+function bbp_bump_user_topic_count( $user_id = 0, $difference = 1 ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Validate user ID
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Check meta for count, or query directly if not found
+	$count = bbp_get_user_topic_count( $user_id, true );
+	if ( empty( $count ) ) {
+		$count = bbp_get_user_topic_count_raw( $user_id );
+	}
+
+	$difference       = (int) $difference;
+	$user_topic_count = (int) ( $count + $difference );
+
+	// Add them up and filter them
+	$new_count = (int) apply_filters( 'bbp_bump_user_topic_count', $user_topic_count, $user_id, $difference, $count );
+
+	return bbp_update_user_topic_count( $user_id, $new_count );
+}
+
+/**
+ * Bump the reply count for a user by a certain amount.
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param int $user_id
+ * @param int $difference
+ */
+function bbp_bump_user_reply_count( $user_id = 0, $difference = 1 ) {
+
+	// Bail if no bump
+	if ( empty( $difference ) ) {
+		return false;
+	}
+
+	// Validate user ID
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Check meta for count, or query directly if not found
+	$count = bbp_get_user_reply_count( $user_id, true );
+	if ( empty( $count ) ) {
+		$count = bbp_get_user_reply_count_raw( $user_id );
+	}
+
+	$difference       = (int) $difference;
+	$user_reply_count = (int) ( $count + $difference );
+
+	// Add them up and filter them
+	$new_count = (int) apply_filters( 'bbp_bump_user_reply_count', $user_reply_count, $user_id, $difference, $count );
+
+	return bbp_update_user_reply_count( $user_id, $new_count );
+}
+
+/**
+ * Helper function used to increase (by one) the count of topics for a user when
+ * a topic is published.
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @access
+ * @param $topic_id
+ * @param $forum_id
+ * @param $anonymous_data
+ * @param $topic_author
+ */
+function bbp_increase_user_topic_count( $topic_id = 0 ) {
+	$user_id = bbp_get_topic_author_id( $topic_id );
+	return bbp_bump_user_topic_count( $user_id, 1 );
+}
+
+/**
+ * Helper function used to increase (by one) the count of replies for a user when
+ * a reply is published.
+ *
+ * This is a helper function, hooked to `bbp_new_reply`
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param $topic_id
+ * @param $forum_id
+ * @param $anonymous_data
+ * @param $topic_author
+ */
+function bbp_increase_user_reply_count( $reply_id = 0 ) {
+	$user_id = bbp_get_reply_author_id( $reply_id );
+	return bbp_bump_user_reply_count( $user_id, 1 );
+}
+
+/**
+ * Helper function used to decrease (by one) the count of topics for a user when
+ * a topic is unpublished.
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param $topic_id
+ */
+function bbp_decrease_user_topic_count( $topic_id = 0 ) {
+	$user_id = bbp_get_topic_author_id( $topic_id );
+	return bbp_bump_user_topic_count( $user_id, -1 );
+}
+
+/**
+ * Helper function used to increase (by one) the count of replies for a user when
+ * a topic is unpublished.
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param $reply_id
+ */
+function bbp_decrease_user_reply_count( $reply_id = 0 ) {
+	$user_id = bbp_get_reply_author_id( $reply_id );
+	return bbp_bump_user_reply_count( $user_id, -1 );
+}
+
+/** Permissions ***************************************************************/
+
+/**
+ * Redirect if unauthorized user is attempting to edit another user
+ *
+ * This is hooked to 'bbp_template_redirect' and controls the conditions under
+ * which a user can edit another user (or themselves.) If these conditions are
+ * met, we assume a user cannot perform this task, and look for ways they can
+ * earn the ability to access this template.
+ *
+ * @since 2.1.0 bbPress (r3605)
+ */
+function bbp_check_user_edit() {
+
+	// Bail if not editing a user
+	if ( ! bbp_is_single_user_edit() ) {
+		return;
+	}
+
+	// Default to false
+	$redirect = true;
+	$user_id  = bbp_get_displayed_user_id();
+
+	// Allow user to edit their own profile
+	if ( bbp_is_user_home_edit() ) {
+		$redirect = false;
+
+	// Allow if current user can edit the displayed user
+	} elseif ( current_user_can( 'edit_user', $user_id ) ) {
+		$redirect = false;
+
+	// Allow if user can manage network users, or edit-any is enabled
+	} elseif ( current_user_can( 'manage_network_users' ) || apply_filters( 'enable_edit_any_user_configuration', false ) ) {
+		$redirect = false;
+	}
+
+	// Allow conclusion to be overridden
+	$redirect = (bool) apply_filters( 'bbp_check_user_edit', $redirect, $user_id );
+
+	// Bail if not redirecting
+	if ( false === $redirect ) {
+		return;
+	}
+
+	// Filter redirect URL
+	$profile_url = bbp_get_user_profile_url( $user_id );
+	$redirect_to = apply_filters( 'bbp_check_user_edit_redirect_to', $profile_url, $user_id );
+
+	// Redirect
+	bbp_redirect( $redirect_to );
+}
+
+/**
+ * Check if a user is blocked, or cannot spectate the forums.
+ *
+ * @since 2.0.0 bbPress (r2996)
+ */
+function bbp_forum_enforce_blocked() {
+
+	// Bail if not logged in or keymaster
+	if ( ! is_user_logged_in() || bbp_is_user_keymaster() ) {
+		return;
+	}
+
+	// Set 404 if in bbPress and user cannot spectate
+	if ( is_bbpress() && ! current_user_can( 'spectate' ) ) {
+		bbp_set_404();
+	}
+}
+
+/** Sanitization **************************************************************/
+
+/**
+ * Sanitize displayed user data, when viewing and editing any user.
+ *
+ * This somewhat monolithic function handles the escaping and sanitization of
+ * user data for a bbPress profile. There are two reasons this all happens here:
+ *
+ * 1. bbPress took a similar approach to WordPress, and funnels all user profile
+ *    data through a central helper. This eventually calls sanitize_user_field()
+ *    which applies a few context based filters, which some third party plugins
+ *    might be relying on bbPress to play nicely with.
+ *
+ * 2. Early versions of bbPress 2.x templates did not escape this data meaning
+ *    a backwards compatible approach like this one was necessary to protect
+ *    existing installations that may have custom template parts.
+ *
+ * @since 2.6.0 bbPress (r5368)
+ *
+ * @param string $value
+ * @param string $field
+ * @param string $context
+ * @return string
+ */
+function bbp_sanitize_displayed_user_field( $value = '', $field = '', $context = 'display' ) {
+
+	// Bail if not editing or displaying (maybe we'll do more here later)
+	if ( ! in_array( $context, array( 'edit', 'display' ), true ) ) {
+		return $value;
+	}
+
+	// By default, no filter set (consider making this an array later)
+	$filter = false;
+
+	// Big switch statement to decide which user field we're sanitizing and how
+	switch ( $field ) {
+
+		// Description is a paragraph
+		case 'description' :
+			$filter = ( 'edit' === $context ) ? '' : 'wp_kses_data';
+			break;
+
+		// Email addresses are sanitized with a specific function
+		case 'user_email'  :
+			$filter = 'sanitize_email';
+			break;
+
+		// Name & login fields
+		case 'user_login'   :
+		case 'display_name' :
+		case 'first_name'   :
+		case 'last_name'    :
+		case 'nick_name'    :
+			$filter = ( 'edit' === $context ) ? 'esc_attr' : 'esc_html';
+			break;
+
+		// wp-includes/default-filters.php escapes this for us via esc_url()
+		case 'user_url' :
+			break;
+	}
+
+	// Run any applicable filters on the value
+	if ( ! empty( $filter ) ) {
+		$value = call_user_func( $filter, $value );
+	}
+
+	return $value;
+}
+
+/** Converter *****************************************************************/
+
+/**
+ * Convert passwords from previous platform encryption to WordPress encryption.
+ *
+ * @since 2.1.0 bbPress (r3813)
+ */
+function bbp_user_maybe_convert_pass() {
+
+	// Sanitize username
+	$username = ! empty( $_POST['log'] )
+		? sanitize_user( $_POST['log'] )
+		: '';
+
+	// Bail if no username
+	if ( empty( $username ) ) {
+		return;
+	}
+
+	// Bail if no user password to convert
+	$bbp_db = bbp_db();
+	$query  = $bbp_db->prepare( "SELECT * FROM {$bbp_db->users} INNER JOIN {$bbp_db->usermeta} ON user_id = ID WHERE meta_key = %s AND user_login = %s LIMIT 1", '_bbp_class', $username );
+	$row    = $bbp_db->get_row( $query );
+	if ( empty( $row ) || is_wp_error( $row ) ) {
+		return;
+	}
+
+	// Setup the converter
+	bbp_setup_converter();
+
+	// Try to convert the old password for this user
+	$converter = bbp_new_converter( $row->meta_value );
+
+	// Try to call the conversion method
+	if ( ( $converter instanceof BBP_Converter_Base ) && method_exists( $converter, 'callback_pass' ) ) {
+		$converter->callback_pass( $username, $_POST['pwd'] );
+	}
+}
Index: /tags/2.6.6/src/includes/users/index.php
===================================================================
--- /tags/2.6.6/src/includes/users/index.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/includes/users/options.php
===================================================================
--- /tags/2.6.6/src/includes/users/options.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/options.php	(revision 7162)
@@ -0,0 +1,355 @@
+<?php
+
+/**
+ * bbPress User Options
+ *
+ * @package bbPress
+ * @subpackage UserOptions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Get the default user options and their values
+ *
+ * @since 2.1.0 bbPress (r3910)
+ *
+ * @return array Filtered user option names and values
+ */
+function bbp_get_default_user_options() {
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_get_default_user_options', array(
+		'_bbp_last_posted' => '0', // For checking flooding
+		'_bbp_topic_count' => '0', // Total topics per site
+		'_bbp_reply_count' => '0'  // Total replies per site
+	) );
+}
+
+/**
+ * Add default user options
+ *
+ * This is destructive, so existing bbPress user options will be overridden.
+ *
+ * @since 2.1.0 bbPress (r3910)
+ */
+function bbp_add_user_options( $user_id = 0 ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return;
+	}
+
+	// Add default options
+	foreach ( bbp_get_default_user_options() as $key => $value ) {
+		update_user_option( $user_id, $key, $value );
+	}
+
+	// Allow previously activated plugins to append their own user options.
+	do_action( 'bbp_add_user_options', $user_id );
+}
+
+/**
+ * Delete default user options
+ *
+ * Hooked to bbp_uninstall, it is only called once when bbPress is uninstalled.
+ * This is destructive, so existing bbPress user options will be destroyed.
+ *
+ * @since 2.1.0 bbPress (r3910)
+ */
+function bbp_delete_user_options( $user_id = 0 ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return;
+	}
+
+	// Add default options
+	foreach ( array_keys( bbp_get_default_user_options() ) as $key ) {
+		delete_user_option( $user_id, $key );
+	}
+
+	// Allow previously activated plugins to append their own options.
+	do_action( 'bbp_delete_user_options', $user_id );
+}
+
+/**
+ * Add filters to each bbPress option and allow them to be overloaded from
+ * inside the $bbp->options array.
+ *
+ * @since 2.1.0 bbPress (r3910)
+ */
+function bbp_setup_user_option_filters() {
+
+	// Add filters to each bbPress option
+	foreach ( array_keys( bbp_get_default_user_options() ) as $key ) {
+		add_filter( 'get_user_option_' . $key, 'bbp_filter_get_user_option', 10, 3 );
+	}
+
+	// Allow previously activated plugins to append their own options.
+	do_action( 'bbp_setup_user_option_filters' );
+}
+
+/**
+ * Filter default options and allow them to be overloaded from inside the
+ * $bbp->user_options array.
+ *
+ * @since 2.1.0 bbPress (r3910)
+ *
+ * @param bool $value Optional. Default value false
+ * @return mixed false if not overloaded, mixed if set
+ */
+function bbp_filter_get_user_option( $value = false, $option = '', $user = 0 ) {
+	$bbp = bbpress();
+
+	// Check the options global for preset value
+	if ( isset( $user->ID ) && isset( $bbp->user_options[ $user->ID ] ) && ! empty( $bbp->user_options[ $user->ID ][ $option ] ) ) {
+		$value = $bbp->user_options[ $user->ID ][ $option ];
+	}
+
+	// Always return a value, even if false
+	return $value;
+}
+
+/** Post Counts ***************************************************************/
+
+/**
+ * Update the topic count for a user
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param int $user_id
+ * @param mixed $count
+ * @return boolean
+ */
+function bbp_update_user_topic_count( $user_id = 0, $count = false ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Just in time filtering of the user's topic count
+	$count = apply_filters( 'bbp_update_user_topic_count', $count, $user_id );
+
+	// Bail if no count was passed
+	if ( false === $count ) {
+		return false;
+	}
+
+	// Return the updated user option
+	return update_user_option( $user_id, '_bbp_topic_count', $count );
+}
+
+/**
+ * Update the reply count for a user
+ *
+ * @since 2.6.0 bbPress (r5309)
+ *
+ * @param int $user_id
+ * @param mixed $count
+ * @return boolean
+ */
+function bbp_update_user_reply_count( $user_id = 0, $count = false ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Just in time filtering of the user's reply count
+	$count = apply_filters( 'bbp_update_user_reply_count', $count, $user_id );
+
+	// Bail if no count was passed
+	if ( false === $count ) {
+		return false;
+	}
+
+	// Return the updated user option
+	return update_user_option( $user_id, '_bbp_reply_count', $count );
+}
+
+/**
+ * Output a users topic count
+ *
+ * @since 2.1.0 bbPress (r3632)
+ *
+ * @param int $user_id
+ * @param boolean $integer Optional. Whether or not to format the result
+ *
+ * @return string
+ */
+function bbp_user_topic_count( $user_id = 0, $integer = false ) {
+	echo esc_html( bbp_get_user_topic_count( $user_id, $integer ) );
+}
+	/**
+	 * Return a users reply count
+	 *
+	 * @since 2.1.0 bbPress (r3632)
+	 *
+	 * @param int $user_id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 *
+	 * @return string
+	 */
+	function bbp_get_user_topic_count( $user_id = 0, $integer = false ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$count  = get_user_option( '_bbp_topic_count', $user_id );
+		$filter = ( true === $integer )
+			? 'bbp_get_user_topic_count_int'
+			: 'bbp_get_user_topic_count';
+
+		// Filter & return
+		return apply_filters( $filter, $count, $user_id );
+	}
+
+/**
+ * Output a users reply count
+ *
+ * @since 2.1.0 bbPress (r3632)
+ *
+ * @param int $user_id
+ * @param boolean $integer Optional. Whether or not to format the result
+ *
+ * @return string
+ */
+function bbp_user_reply_count( $user_id = 0, $integer = false ) {
+	echo esc_html( bbp_get_user_reply_count( $user_id, $integer ) );
+}
+	/**
+	 * Return a users reply count
+	 *
+	 * @since 2.1.0 bbPress (r3632)
+	 *
+	 * @param int $user_id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 *
+	 * @return string
+	 */
+	function bbp_get_user_reply_count( $user_id = 0, $integer = false ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$count  = get_user_option( '_bbp_reply_count', $user_id );
+		$filter = ( true === $integer )
+			? 'bbp_get_user_reply_count_int'
+			: 'bbp_get_user_reply_count';
+
+		return apply_filters( $filter, $count, $user_id );
+	}
+
+/**
+ * Output a users total post count
+ *
+ * @since 2.1.0 bbPress (r3632)
+ *
+ * @param int $user_id
+ * @param boolean $integer Optional. Whether or not to format the result
+ *
+ * @return string
+ */
+function bbp_user_post_count( $user_id = 0, $integer = false ) {
+	echo esc_html( bbp_get_user_post_count( $user_id, $integer ) );
+}
+	/**
+	 * Return a users total post count
+	 *
+	 * @since 2.1.0 bbPress (r3632)
+	 *
+	 * @param int $user_id
+	 * @param boolean $integer Optional. Whether or not to format the result
+	 *
+	 * @return string
+	 */
+	function bbp_get_user_post_count( $user_id = 0, $integer = false ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$topics  = bbp_get_user_topic_count( $user_id, true );
+		$replies = bbp_get_user_reply_count( $user_id, true );
+		$count   = $topics + $replies;
+		$filter  = ( true === $integer )
+			? 'bbp_get_user_post_count_int'
+			: 'bbp_get_user_post_count';
+
+		return apply_filters( $filter, $count, $user_id );
+	}
+
+/** Last Posted ***************************************************************/
+
+/**
+ * Update a users last posted time, for use with post throttling
+ *
+ * @since 2.1.0 bbPress (r3910)
+ *
+ * @param int $user_id User ID to update
+ * @param int $time Time in time() format
+ * @return bool False if no user or failure, true if successful
+ */
+function bbp_update_user_last_posted( $user_id = 0, $time = 0 ) {
+
+	// Validate user id
+	$user_id = bbp_get_user_id( $user_id );
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Set time to now if nothing is passed
+	if ( empty( $time ) ) {
+		$time = time();
+	}
+
+	return update_user_option( $user_id, '_bbp_last_posted', $time );
+}
+
+/**
+ * Output the raw value of the last posted time.
+ *
+ * @since 2.1.0 bbPress (r3910)
+ *
+ * @param int $user_id User ID to retrieve value for
+ */
+function bbp_user_last_posted( $user_id = 0 ) {
+	echo esc_html( bbp_get_user_last_posted( $user_id ) );
+}
+
+	/**
+	 * Return the raw value of the last posted time.
+	 *
+	 * @since 2.1.0 bbPress (r3910)
+	 *
+	 * @param int $user_id User ID to retrieve value for
+	 * @return mixed False if no user, time() format if exists
+	 */
+	function bbp_get_user_last_posted( $user_id = 0 ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$time = get_user_option( '_bbp_last_posted', $user_id );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_last_posted', $time, $user_id );
+	}
Index: /tags/2.6.6/src/includes/users/signups.php
===================================================================
--- /tags/2.6.6/src/includes/users/signups.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/signups.php	(revision 7162)
@@ -0,0 +1,263 @@
+<?php
+
+/**
+ * bbPress Signups
+ *
+ * This file contains functions for assisting with adding forum data to user
+ * accounts during signup, account creation, and invitation.
+ *
+ * @package bbPress
+ * @subpackage Signups
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the forum-role field when adding a new user
+ *
+ * @since 2.6.0 bbPress (r6674)
+ */
+function bbp_add_user_form_role_field() {
+
+	// Bail if current user cannot promote users
+	if ( ! current_user_can( 'promote_users' ) ) {
+		return;
+	} ?>
+
+	<table class="form-table">
+		<tr class="form-field">
+			<th scope="row"><label for="bbp-forums-role"><?php esc_html_e( 'Forum Role', 'bbpress' ); ?></label></th>
+			<td><?php
+
+				// Default user role
+				$default_role  = isset( $_POST['bbp-forums-role'] )
+					? sanitize_key( $_POST['bbp-forums-role'] )
+					: bbp_get_default_role();
+
+				// Get the folum roles
+				$dynamic_roles = bbp_get_dynamic_roles();
+
+				// Only keymasters can set other keymasters
+				if ( ! bbp_is_user_keymaster() ) {
+					unset( $dynamic_roles[ bbp_get_keymaster_role() ] );
+				} ?>
+
+				<select name="bbp-forums-role" id="bbp-forums-role">
+
+					<?php foreach ( $dynamic_roles as $role => $details ) : ?>
+
+						<option <?php selected( $default_role, $role ); ?> value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+
+					<?php endforeach; ?>
+
+				</select>
+			</td>
+		</tr>
+	</table>
+
+<?php
+}
+
+/**
+ * Maybe add forum role to signup meta array
+ *
+ * @since 2.6.0 bbPress (r6674)
+ *
+ * @param array $meta
+ *
+ * @return array
+ */
+function bbp_user_add_role_to_signup_meta( $meta = array() ) {
+
+	// Bail if already added
+	if ( ! empty( $meta['bbp_new_role'] ) ) {
+		return $meta;
+	}
+
+	// Role to validate
+	$to_validate = ! empty( $_POST['bbp-forums-role'] ) && is_string( $_POST['bbp-forums-role'] )
+		? sanitize_key( $_POST['bbp-forums-role'] )
+		: '';
+
+	// Validate the signup role
+	$valid_role = bbp_validate_registration_role( $to_validate );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return $meta;
+	}
+
+	// Add role to meta
+	$meta['bbp_new_role'] = $valid_role;
+
+	// Return meta
+	return $meta;
+}
+
+/**
+ * Add forum meta data when inviting a user to a site
+ *
+ * @since 2.6.0 bbPress (r6674)
+ *
+ * @param int    $user_id     The invited user's ID.
+ * @param array  $role        The role of invited user.
+ * @param string $newuser_key The key of the invitation.
+ */
+function bbp_user_add_role_on_invite( $user_id = '', $role = '', $newuser_key = '' ) {
+
+	// Role to validate
+	$to_validate = ! empty( $_POST['bbp-forums-role'] ) && is_string( $_POST['bbp-forums-role'] )
+		? sanitize_key( $_POST['bbp-forums-role'] )
+		: '';
+
+	// Validate the signup role
+	$valid_role = bbp_validate_registration_role( $to_validate );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	// Option key
+	$option_key = 'new_user_' . $newuser_key;
+
+	// Get the user option
+	$user_option = get_option( $option_key, array() );
+
+	// Add the new role
+	$user_option['bbp_new_role'] = $valid_role;
+
+	// Update the invitation
+	update_option( $option_key, $user_option );
+}
+
+/**
+ * Single-site handler for adding a new user
+ *
+ * @since 2.6.0 bbPress (r6674)
+ *
+ * @param int $user_id
+ */
+function bbp_user_add_role_on_register( $user_id = '' ) {
+
+	// Role to validate
+	$to_validate = ! empty( $_POST['bbp-forums-role'] ) && is_string( $_POST['bbp-forums-role'] )
+		? sanitize_key( $_POST['bbp-forums-role'] )
+		: '';
+
+	// Validate the signup role
+	$valid_role = bbp_validate_registration_role( $to_validate );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	// Set the user role
+	bbp_set_user_role( $user_id, $valid_role );
+}
+
+/**
+ * Multi-site handler for adding a new user
+ *
+ * @since 2.6.0 bbPress (r6674)
+ *
+ * @param int $user_id User ID.
+ */
+function bbp_user_add_role_on_activate( $user_id = 0, $password = '', $meta = array() ) {
+
+	// Role to validate
+	$to_validate = ! empty( $meta['bbp_new_role'] ) && is_string( $meta['bbp_new_role'] )
+		? sanitize_key( $meta['bbp_new_role'] )
+		: '';
+
+	// Validate the signup role
+	$valid_role = bbp_validate_activation_role( $to_validate );
+
+	// Bail if errors
+	if ( bbp_has_errors() ) {
+		return;
+	}
+
+	// Set the user role
+	bbp_set_user_role( $user_id, $valid_role );
+}
+
+/** Validators ****************************************************************/
+
+/**
+ * Validate the Forum role during signup
+ *
+ * This helper function performs a number of generic checks, and encapsulates
+ * the logic used to validate if a Forum Role is valid, typically during new
+ * user registration, but also when adding an existing user to a site in
+ * Multisite installations.
+ *
+ * @since 2.6.5
+ *
+ * @param string $to_validate A role ID to validate
+ * @return string A valid role ID, or empty string on error
+ */
+function bbp_validate_signup_role( $to_validate = '' ) {
+
+	// Default return value
+	$retval = '';
+
+	// Add error if role is empty
+	if ( empty( $to_validate ) ) {
+		bbp_add_error( 'bbp_signup_role_empty', __( '<strong>Error</strong>: Empty role.', 'bbpress' ) );
+	}
+
+	// Add error if posted role is not a valid role
+	if ( ! bbp_is_valid_role( $to_validate ) ) {
+		bbp_add_error( 'bbp_signup_role_invalid', __( '<strong>Error</strong>: Invalid role.', 'bbpress' ) );
+	}
+
+	// If no errors, set return value to the role to validate
+	if ( ! bbp_has_errors() ) {
+		$retval = $to_validate;
+	}
+
+	// Filter & return
+	return (string) apply_filters( 'bbp_validate_signup_role', $retval, $to_validate );
+}
+
+/**
+ * Validate the Forum role during the registration process
+ *
+ * @since 2.6.5
+ *
+ * @param string $to_validate A well-formed (string) role ID to validate
+ * @return string A valid role ID, or empty string on error
+ */
+function bbp_validate_registration_role( $to_validate = '' ) {
+
+	// Default return value
+	$retval = bbp_get_default_role();
+
+	// Conditionally handle posted values for capable users
+	if ( is_admin() && current_user_can( 'create_users' ) ) {
+		$retval = $to_validate;
+	}
+
+	// Validate & return
+	return bbp_validate_signup_role( $retval );
+}
+
+/**
+ * Validate the Forum role during activation
+ *
+ * This function exists simply for parity with registrations, and to maintain an
+ * intentional layer of abstraction from the more generic function it uses.
+ *
+ * @since 2.6.5
+ *
+ * @param string $to_validate A well-formed (string) role ID to validate
+ * @return string A valid role ID, or empty string on error
+ */
+function bbp_validate_activation_role( $to_validate = '' ) {
+
+	// Validate & return
+	return bbp_validate_signup_role( $to_validate );
+}
Index: /tags/2.6.6/src/includes/users/template.php
===================================================================
--- /tags/2.6.6/src/includes/users/template.php	(revision 7162)
+++ /tags/2.6.6/src/includes/users/template.php	(revision 7162)
@@ -0,0 +1,2405 @@
+<?php
+
+/**
+ * bbPress User Template Tags
+ *
+ * @package bbPress
+ * @subpackage TemplateTags
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** User Loop *****************************************************************/
+
+/**
+ * Extension of WP_User_Query to allow easy looping
+ *
+ * @since 2.6.0 bbPress (r6330)
+ */
+class BBP_User_Query extends WP_User_Query {
+
+	/**
+	 * The amount of users for the current query.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 * @var int
+	 */
+	public $user_count = 0;
+
+	/**
+	 * Index of the current item in the loop.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 * @var int
+	 */
+	public $current_user = -1;
+
+	/**
+	 * Whether the loop has started and the caller is in the loop.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 * @var bool
+	 */
+	public $in_the_loop = false;
+
+	/**
+	 * The current user.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 * @var WP_User
+	 */
+	public $user;
+
+	/**
+	 * PHP5 constructor.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 *
+	 * @param null|string|array $query Optional. The query variables.
+	 */
+	public function __construct( $query = null ) {
+		if ( ! empty( $query ) ) {
+			parent::__construct( $query );
+			$this->user_count = count( $this->results );
+		}
+	}
+
+	/**
+	 * Set up the next user and iterate current user index.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 *
+	 * @return WP_User Next user.
+	 */
+	public function next_user() {
+		$this->current_user++;
+		$this->user = $this->results[ $this->current_user ];
+
+		return $this->user;
+	}
+
+	/**
+	 * Sets up the current user.
+	 *
+	 * Retrieves the next user, sets up the user, sets the 'in the loop'
+	 * property to true.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 *
+	 * @global WP_User $user
+	 */
+	public function the_user() {
+		$this->in_the_loop = true;
+
+		// loop has just started
+		if ( $this->current_user === -1 ) {
+
+			/**
+			 * Fires once the loop is started.
+			 *
+			 * @since 2.6.0 bbPress (r6330)
+			 *
+			 * @param WP_Query &$this The WP_Query instance (passed by reference).
+			 */
+			do_action_ref_array( 'loop_start', array( &$this ) );
+		}
+
+		$this->next_user();
+	}
+
+	/**
+	 * Determines whether there are more users available in the loop.
+	 *
+	 * Calls the {@see 'loop_end'} action when the loop is complete.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 *
+	 * @return bool True if users are available, false if end of loop.
+	 */
+	public function have_users() {
+		if ( ( $this->current_user + 1 ) < $this->user_count ) {
+			return true;
+		} elseif ( ( ( $this->current_user + 1 ) === $this->user_count ) && ( $this->user_count > 0 ) ) {
+
+			/**
+			 * Fires once the loop has ended.
+			 *
+			 * @since 2.6.0 bbPress (r6330)
+			 *
+			 * @param WP_Query &$this The WP_Query instance (passed by reference).
+			 */
+			do_action_ref_array( 'loop_end', array( &$this ) );
+
+			// Do some cleaning up after the loop
+			$this->rewind_users();
+		}
+
+		$this->in_the_loop = false;
+
+		return false;
+	}
+
+	/**
+	 * Rewind the users and reset user index.
+	 *
+	 * @since 2.6.0 bbPress (r6330)
+	 * @access public
+	 */
+	public function rewind_users() {
+		$this->current_user = -1;
+
+		if ( $this->user_count > 0 ) {
+			$this->user = $this->results[ 0 ];
+		}
+	}
+}
+
+/**
+ * The main user loop.
+ *
+ * @since 2.6.0 bbPress (r6330)
+ *
+ * @param array $args All the arguments supported by {@link WP_User_Query}
+ * @return object Multidimensional array of user information
+ */
+function bbp_has_users( $args = array() ) {
+
+	// Parse arguments with default user query for most circumstances
+	$r = bbp_parse_args( $args, array(
+		'include'     => array(),
+		'orderby'     => 'login',
+		'order'       => 'ASC',
+		'count_total' => false,
+		'fields'      => 'all',
+	), 'has_users' );
+
+	// Run the query
+	$bbp             = bbpress();
+	$bbp->user_query = new BBP_User_Query( $r );
+
+	// Filter & return
+	return apply_filters( 'bbp_has_users', $bbp->user_query->have_users(), $bbp->user_query );
+}
+
+/**
+ * Whether there are more users available in the loop
+ *
+ * @since 2.6.0 bbPress (r2464)
+ *
+ * @return object User information
+ */
+function bbp_users() {
+	return bbpress()->user_query->have_users();
+}
+
+/**
+ * Loads up the current user in the loop
+ *
+ * @since 2.6.0 bbPress (r2464)
+ *
+ * @return object User information
+ */
+function bbp_the_user() {
+	return bbpress()->user_query->the_user();
+}
+
+/** Users *********************************************************************/
+
+/**
+ * Output a validated user id
+ *
+ * @since 2.0.0 bbPress (r2729)
+ *
+ * @param int $user_id Optional. User id
+ * @param bool $displayed_user_fallback Fallback on displayed user?
+ * @param bool $current_user_fallback Fallback on current user?
+ */
+function bbp_user_id( $user_id = 0, $displayed_user_fallback = true, $current_user_fallback = false ) {
+	echo bbp_get_user_id( $user_id, $displayed_user_fallback, $current_user_fallback );
+}
+	/**
+	 * Return a validated user id
+	 *
+	 * @since 2.0.0 bbPress (r2729)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @param bool $displayed_user_fallback Fallback on displayed user?
+	 * @param bool $current_user_fallback Fallback on current user?
+	 * @return int Validated user id
+	 */
+	function bbp_get_user_id( $user_id = 0, $displayed_user_fallback = true, $current_user_fallback = false ) {
+		$bbp = bbpress();
+
+		// Easy empty checking
+		if ( ! empty( $user_id ) && is_numeric( $user_id ) ) {
+			$bbp_user_id = $user_id;
+
+		// Currently inside a user loop
+		} elseif ( ! empty( $bbp->user_query->in_the_loop ) && isset( $bbp->user_query->user->ID ) ) {
+			$bbp_user_id = $bbp->user_query->user->ID;
+
+		// Currently viewing or editing a user
+		} elseif ( ( true === $displayed_user_fallback ) && ! empty( $bbp->displayed_user->ID ) ) {
+			$bbp_user_id = $bbp->displayed_user->ID;
+
+		// Maybe fallback on the current_user ID
+		} elseif ( ( true === $current_user_fallback ) && ! empty( $bbp->current_user->ID ) ) {
+			$bbp_user_id = $bbp->current_user->ID;
+
+		// Failsafe
+		} else {
+			$bbp_user_id = 0;
+		}
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_user_id', (int) $bbp_user_id, $displayed_user_fallback, $current_user_fallback );
+	}
+
+/**
+ * Output ID of current user
+ *
+ * @since 2.0.0 bbPress (r2574)
+ */
+function bbp_current_user_id() {
+	echo bbp_get_current_user_id();
+}
+	/**
+	 * Return ID of current user
+	 *
+	 * @since 2.0.0 bbPress (r2574)
+	 *
+	 * @return int Current user id
+	 */
+	function bbp_get_current_user_id() {
+
+		// Filter & return
+		return (int) apply_filters( 'bbp_get_current_user_id', bbp_get_user_id( 0, false, true ) );
+	}
+
+/**
+ * Output ID of displayed user
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_displayed_user_id() {
+	echo bbp_get_displayed_user_id();
+}
+	/**
+	 * Return ID of displayed user
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @return int Displayed user id
+	 */
+	function bbp_get_displayed_user_id() {
+
+		// Filter & return
+		return apply_filters( 'bbp_get_displayed_user_id', bbp_get_user_id( 0, true, false ) );
+	}
+
+/**
+ * Output a sanitized user field value
+ *
+ * This function relies on the $filter parameter to decide how to sanitize
+ * the field value that it finds. Since it uses the WP_User object's magic
+ * __get() method, it can also be used to get user_meta values.
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param string $field Field to get
+ * @param string $filter How to filter the field value (null|raw|db|display|edit)
+ */
+function bbp_displayed_user_field( $field = '', $filter = 'display' ) {
+	echo bbp_get_displayed_user_field( $field, $filter );
+}
+	/**
+	 * Return a sanitized user field value
+	 *
+	 * This function relies on the $filter parameter to decide how to sanitize
+	 * the field value that it finds. Since it uses the WP_User object's magic
+	 * __get() method, it can also be used to get user_meta values.
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param string $field Field to get
+	 * @param string $filter How to filter the field value (null|raw|db|display|edit)
+	 * @see WP_User::__get() for more on how the value is retrieved
+	 * @see sanitize_user_field() for more on how the value is sanitized
+	 * @return string|bool Value of the field if it exists, else false
+	 */
+	function bbp_get_displayed_user_field( $field = '', $filter = 'display' ) {
+
+		// Get the displayed user
+		$user         = bbpress()->displayed_user;
+
+		// Juggle the user filter property because we don't want to muck up how
+		// other code might interact with this object.
+		$old_filter   = $user->filter;
+		$user->filter = $filter;
+
+		// Get the field value from the WP_User object. We don't need to perform
+		// an isset() because the WP_User::__get() does it for us.
+		$value        = $user->$field;
+
+		// Put back the user filter property that was previously juggled above.
+		$user->filter = $old_filter;
+
+		// Filter & return
+		return apply_filters( 'bbp_get_displayed_user_field', $value, $field, $filter );
+	}
+
+/**
+ * Output name of current user
+ *
+ * @since 2.0.0 bbPress (r2574)
+ */
+function bbp_current_user_name() {
+	echo esc_attr( bbp_get_current_user_name() );
+}
+	/**
+	 * Return name of current user
+	 *
+	 * @since 2.0.0 bbPress (r2574)
+	 *
+	 * @return string
+	 */
+	function bbp_get_current_user_name() {
+
+		// Default current user name
+		$default = bbp_get_fallback_display_name();
+
+		// Check the $user_identity global
+		$current_user_name = is_user_logged_in()
+			? bbp_get_global_object( 'user_identity', false, $default )
+			: $default;
+
+		// Filter & return
+		return apply_filters( 'bbp_get_current_user_name', $current_user_name );
+	}
+
+/**
+ * Output avatar of current user
+ *
+ * @since 2.0.0 bbPress (r2574)
+ *
+ * @param int $size Size of the avatar. Defaults to 40
+ */
+function bbp_current_user_avatar( $size = 40 ) {
+	echo bbp_get_current_user_avatar( $size );
+}
+
+	/**
+	 * Return avatar of current user
+	 *
+	 * @since 2.0.0 bbPress (r2574)
+	 *
+	 * @param int $size Size of the avatar. Defaults to 40
+	 * @return string Current user avatar
+	 */
+	function bbp_get_current_user_avatar( $size = 40 ) {
+
+		$user = bbp_get_current_user_id();
+		if ( empty( $user ) ) {
+			$user = bbp_get_current_anonymous_user_data( 'email' );
+		}
+
+		$avatar = get_avatar( $user, $size );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_current_user_avatar', $avatar, $size );
+	}
+
+/**
+ * Output link to the profile page of a user
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_user_profile_link( $user_id = 0 ) {
+	echo bbp_get_user_profile_link( $user_id );
+}
+	/**
+	 * Return link to the profile page of a user
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string User profile link
+	 */
+	function bbp_get_user_profile_link( $user_id = 0 ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$user      = get_userdata( $user_id );
+		$user_link = '<a href="' . esc_url( bbp_get_user_profile_url( $user_id ) ) . '">' . esc_html( $user->display_name ) . '</a>';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_profile_link', $user_link, $user_id );
+	}
+
+/**
+ * Output a users nicename to the screen
+ *
+ * @since 2.3.0 bbPress (r4671)
+ *
+ * @param int $user_id User ID whose nicename to get
+ * @param array $args before|after|user_id|force
+ */
+function bbp_user_nicename( $user_id = 0, $args = array() ) {
+	echo bbp_get_user_nicename( $user_id, $args );
+}
+	/**
+	 * Return a users nicename to the screen
+	 *
+	 * @since 2.3.0 bbPress (r4671)
+	 *
+	 * @param int $user_id User ID whose nicename to get
+	 * @param array $args before|after|user_id|force
+	 * @return string User nicename, maybe wrapped in before/after strings
+	 */
+	function bbp_get_user_nicename( $user_id = 0, $args = array() ) {
+
+		// Bail if no user ID passed
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Parse default arguments
+		$r = bbp_parse_args( $args, array(
+			'user_id' => $user_id,
+			'before'  => '',
+			'after'   => '',
+			'force'   => ''
+		), 'get_user_nicename' );
+
+		// Force the nicename (likely from a previous user query)
+		if ( ! empty( $r['force'] ) ) {
+			$nicename = (string) $r['force'];
+
+		// Maybe fallback to getting the nicename from user data
+		} elseif ( ! empty( $r['user_id'] ) ) {
+			$user     = get_userdata( $r['user_id'] );
+			$nicename = ! empty( $user )
+				? $user->user_nicename
+				: '';
+
+		// Maybe fallback to empty string so filter still applies
+		} else {
+			$nicename = '';
+		}
+
+		// Maybe wrap the nicename
+		$retval = ! empty( $nicename )
+			? $r['before'] . esc_html( $nicename ) . $r['after']
+			: '';
+
+		// Filter & return
+		return (string) apply_filters( 'bbp_get_user_nicename', $retval, $user_id, $r, $args );
+	}
+
+/**
+ * Output URL to the profile page of a user
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param int $user_id Optional. User id
+ * @param string $user_nicename Optional. User nicename
+ */
+function bbp_user_profile_url( $user_id = 0, $user_nicename = '' ) {
+	echo esc_url( bbp_get_user_profile_url( $user_id, $user_nicename ) );
+}
+	/**
+	 * Return URL to the profile page of a user
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @param string $user_nicename Optional. User nicename
+	 * @return string User profile url
+	 */
+	function bbp_get_user_profile_url( $user_id = 0, $user_nicename = '' ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_user_profile_url', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Get username if not passed
+			if ( empty( $user_nicename ) ) {
+				$user_nicename = bbp_get_user_nicename( $user_id );
+			}
+
+			// Run through home_url()
+			$url = trailingslashit( bbp_get_root_url() . bbp_get_user_slug() ) . $user_nicename;
+			$url = user_trailingslashit( $url );
+			$url = home_url( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_user_rewrite_id() => $user_id
+			), home_url( '/' ) );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_profile_url', $url, $user_id, $user_nicename );
+	}
+
+/**
+ * Output link to the profile edit page of a user
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_user_profile_edit_link( $user_id = 0 ) {
+	echo bbp_get_user_profile_edit_link( $user_id );
+}
+	/**
+	 * Return link to the profile edit page of a user
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string User profile edit link
+	 */
+	function bbp_get_user_profile_edit_link( $user_id = 0 ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$user      = get_userdata( $user_id );
+		$edit_link = '<a href="' . esc_url( bbp_get_user_profile_edit_url( $user_id ) ) . '">' . esc_html( $user->display_name ) . '</a>';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_profile_edit_link', $edit_link, $user_id );
+	}
+
+/**
+ * Output URL to the profile edit page of a user
+ *
+ * @since 2.0.0 bbPress (r2688)
+ *
+ * @param int $user_id Optional. User id
+ * @param string $user_nicename Optional. User nicename
+ */
+function bbp_user_profile_edit_url( $user_id = 0, $user_nicename = '' ) {
+	echo esc_url( bbp_get_user_profile_edit_url( $user_id, $user_nicename ) );
+}
+	/**
+	 * Return URL to the profile edit page of a user
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @param string $user_nicename Optional. User nicename
+	 * @return string
+	 */
+	function bbp_get_user_profile_edit_url( $user_id = 0, $user_nicename = '' ) {
+
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_user_profile_edit_url', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL
+		$profile_url = bbp_get_user_profile_url( $user_id, $user_nicename );
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+			$url = trailingslashit( $profile_url ) . bbp_get_edit_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_edit_rewrite_id() => '1'
+			), $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_edit_profile_url', $url, $user_id, $user_nicename );
+	}
+
+/**
+ * Output a user's main role for display
+ *
+ * @since 2.1.0 bbPress (r3860)
+ *
+ * @param int $user_id
+ */
+function bbp_user_display_role( $user_id = 0 ) {
+	echo esc_attr( bbp_get_user_display_role( $user_id ) );
+}
+	/**
+	 * Return a user's main role for display
+	 *
+	 * @since 2.1.0 bbPress (r3860)
+	 *
+	 * @param int $user_id
+	 * @return string
+	 */
+	function bbp_get_user_display_role( $user_id = 0 ) {
+
+		// Validate user id
+		$user_id = bbp_get_user_id( $user_id );
+
+		// User is not registered
+		if ( empty( $user_id ) ) {
+			$role = esc_html__( 'Guest', 'bbpress' );
+
+		// User is not active
+		} elseif ( bbp_is_user_inactive( $user_id ) ) {
+			$role = esc_html__( 'Inactive', 'bbpress' );
+
+		// User have a role
+		} else {
+			$role_id = bbp_get_user_role( $user_id );
+			$role    = bbp_get_dynamic_role_name( $role_id );
+		}
+
+		// No role found so default to generic "Member"
+		if ( empty( $role ) ) {
+			$role = esc_html__( 'Member', 'bbpress' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_display_role', $role, $user_id );
+	}
+
+/**
+ * Output the link to the admin section
+ *
+ * @since 2.0.0 bbPress (r2827)
+ *
+ * @param array $args Optional. See {@link bbp_get_admin_link()}
+ */
+function bbp_admin_link( $args = array() ) {
+	echo bbp_get_admin_link( $args );
+}
+	/**
+	 * Return the link to the admin section
+	 *
+	 * @since 2.0.0 bbPress (r2827)
+	 *
+	 * @param array $args Optional. This function supports these arguments:
+	 *  - text: The text
+	 *  - before: Before the lnk
+	 *  - after: After the link
+	 * @return The link
+	 */
+	function bbp_get_admin_link( $args = array() ) {
+
+		// Bail if user cannot globally moderate
+		if ( ! current_user_can( 'moderate' ) ) {
+			return;
+		}
+
+		if ( ! empty( $args ) && is_string( $args ) && ( false === strpos( $args, '=' ) ) ) {
+			$args = array( 'text' => $args );
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'text'   => esc_html__( 'Admin', 'bbpress' ),
+			'before' => '',
+			'after'  => ''
+		), 'get_admin_link' );
+
+		$retval = $r['before'] . '<a href="' . esc_url( admin_url() ) . '">' . $r['text'] . '</a>' . $r['after'];
+
+		// Filter & return
+		return apply_filters( 'bbp_get_admin_link', $retval, $r, $args );
+	}
+
+/** User IP *******************************************************************/
+
+/**
+ * Output the author IP address of a post
+ *
+ * @since 2.0.0 bbPress (r3120)
+ *
+ * @param array $args Optional. If it is an integer, it is used as post id.
+ */
+function bbp_author_ip( $args = array() ) {
+	echo bbp_get_author_ip( $args );
+}
+	/**
+	 * Return the author IP address of a post
+	 *
+	 * @since 2.0.0 bbPress (r3120)
+	 *
+	 * @param array $args Optional. If an integer, it is used as reply id.
+	 * @return string Author link of reply
+	 */
+	function bbp_get_author_ip( $args = array() ) {
+
+		// Used as post id
+		$post_id = is_numeric( $args ) ? (int) $args : 0;
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'post_id' => $post_id,
+			'before'  => '<span class="bbp-author-ip">(',
+			'after'   => ')</span>'
+		), 'get_author_ip' );
+
+		// Get the author IP meta value
+		$author_ip = get_post_meta( $r['post_id'], '_bbp_author_ip', true );
+		$author_ip = ! empty( $author_ip )
+			? $r['before'] . esc_html( $author_ip ) . $r['after']
+			: '';
+
+		// Filter & return
+		return apply_filters( 'bbp_get_author_ip', $author_ip, $r, $args );
+	}
+
+/** Anonymous Fields **********************************************************/
+
+/**
+ * Get the default name that's displayed when a user cannot be identified.
+ *
+ * This might happen if a user was deleted but their content was retained, or
+ * if something went wrong during saving anonymous user data to the database.
+ *
+ * @since 2.6.0 bbPress (r6561)
+ *
+ * @param int $object_id For additional context only, usually a post ID
+ *
+ * @return string
+ */
+function bbp_get_fallback_display_name( $object_id = 0 ) {
+	$name = esc_html__( 'Anonymous', 'bbpress' );
+
+	// Filter & return
+	return apply_filters( 'bbp_get_anonymous_name', $name, $object_id );
+}
+
+/**
+ * Output the author display-name of a topic or reply.
+ *
+ * Convenience function to ensure proper template functions are called
+ * and correct filters are executed. Used primarily to display topic
+ * and reply author information in the anonymous form template-part.
+ *
+ * @since 2.5.0 bbPress (r5119)
+ *
+ * @param int $post_id
+ */
+function bbp_author_display_name( $post_id = 0 ) {
+	echo esc_attr( bbp_get_author_display_name( $post_id ) );
+}
+
+	/**
+	 * Return the author display-name of a topic or reply.
+	 *
+	 * Convenience function to ensure proper template functions are called
+	 * and correct filters are executed. Used primarily to display topic
+	 * and reply author information in the anonymous form template-part.
+	 *
+	 * @since 2.5.0 bbPress (r5119)
+	 *
+	 * @param int $post_id
+	 *
+	 * @return string The name of the author
+	 */
+	function bbp_get_author_display_name( $post_id = 0 ) {
+
+		// Define local variable(s)
+		$retval = '';
+
+		// Topic edit
+		if ( bbp_is_topic_edit() ) {
+			$retval = bbp_get_topic_author_display_name( $post_id );
+
+		// Reply edit
+		} elseif ( bbp_is_reply_edit() ) {
+			$retval = bbp_get_reply_author_display_name( $post_id );
+
+		// Not an edit, so rely on current user cookie data
+		} else {
+			$retval = bbp_get_current_anonymous_user_data( 'name' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_author_display_name', $retval, $post_id );
+	}
+
+/**
+ * Output the author email of a topic or reply.
+ *
+ * Convenience function to ensure proper template functions are called
+ * and correct filters are executed. Used primarily to display topic
+ * and reply author information in the anonymous user form template-part.
+ *
+ * @since 2.5.0 bbPress (r5119)
+ *
+ * @param int $post_id
+ */
+function bbp_author_email( $post_id = 0 ) {
+	echo esc_attr( bbp_get_author_email( $post_id ) );
+}
+
+	/**
+	 * Return the author email of a topic or reply.
+	 *
+	 * Convenience function to ensure proper template functions are called
+	 * and correct filters are executed. Used primarily to display topic
+	 * and reply author information in the anonymous user form template-part.
+	 *
+	 * @since 2.5.0 bbPress (r5119)
+	 *
+	 * @param int $post_id
+	 *
+	 * @return string The email of the author
+	 */
+	function bbp_get_author_email( $post_id = 0 ) {
+
+		// Define local variable(s)
+		$retval = '';
+
+		// Topic edit
+		if ( bbp_is_topic_edit() ) {
+			$retval = bbp_get_topic_author_email( $post_id );
+
+		// Reply edit
+		} elseif ( bbp_is_reply_edit() ) {
+			$retval = bbp_get_reply_author_email( $post_id );
+
+		// Not an edit, so rely on current user cookie data
+		} else {
+			$retval = bbp_get_current_anonymous_user_data( 'email' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_author_email', $retval, $post_id );
+	}
+
+/**
+ * Output the author url of a topic or reply.
+ *
+ * Convenience function to ensure proper template functions are called
+ * and correct filters are executed. Used primarily to display topic
+ * and reply author information in the anonymous user form template-part.
+ *
+ * @since 2.5.0 bbPress (r5119)
+ *
+ * @param int $post_id
+ */
+function bbp_author_url( $post_id = 0 ) {
+	echo esc_attr( bbp_get_author_url( $post_id ) );
+}
+
+	/**
+	 * Return the author url of a topic or reply.
+	 *
+	 * Convenience function to ensure proper template functions are called
+	 * and correct filters are executed. Used primarily to display topic
+	 * and reply author information in the anonymous user form template-part.
+	 *
+	 * @since 2.5.0 bbPress (r5119)
+	 *
+	 * @param int $post_id
+	 *
+	 * @return string The url of the author
+	 */
+	function bbp_get_author_url( $post_id = 0 ) {
+
+		// Define local variable(s)
+		$retval = '';
+
+		// Topic edit
+		if ( bbp_is_topic_edit() ) {
+			$retval = bbp_get_topic_author_url( $post_id );
+
+		// Reply edit
+		} elseif ( bbp_is_reply_edit() ) {
+			$retval = bbp_get_reply_author_url( $post_id );
+
+		// Not an edit, so rely on current user cookie data
+		} else {
+			$retval = bbp_get_current_anonymous_user_data( 'url' );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_author_url', $retval, $post_id );
+	}
+
+/** Favorites *****************************************************************/
+
+/**
+ * Output the link to the user's favorites page (profile page)
+ *
+ * @since 2.0.0 bbPress (r2652)
+ * @since 2.6.0 bbPress (r6308) Add pagination if in the loop
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_favorites_permalink( $user_id = 0 ) {
+	echo esc_url( bbp_get_favorites_permalink( $user_id ) );
+}
+	/**
+	 * Return the link to the user's favorites page (profile page)
+	 *
+	 * @since 2.0.0 bbPress (r2652)
+	 * @since 2.6.0 bbPress (r6308) Add pagination if in the loop
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string Permanent link to user profile page
+	 */
+	function bbp_get_favorites_permalink( $user_id = 0 ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_favorites_permalink', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL & page
+		$profile_url = bbp_get_user_profile_url( $user_id );
+		$page        = (int)  bbpress()->topic_query->paged;
+		$paged       = (bool) bbpress()->topic_query->in_the_loop;
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Base URL
+			$url = trailingslashit( $profile_url ) . bbp_get_user_favorites_slug();
+
+			// Add page
+			if ( ( true === $paged ) && ( $page > 1 ) ) {
+				$url = trailingslashit( $url ) . bbp_get_paged_slug() . '/' . $page;
+			}
+
+			// Ensure correct trailing slash
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+
+			// Base arguments
+			$args = array(
+				bbp_get_user_favorites_rewrite_id() => bbp_get_user_favorites_slug(),
+			);
+
+			// Add page
+			if ( ( true === $paged ) && ( $page > 1 ) ) {
+				$args['page'] = $page;
+			}
+
+			// Add arguments
+			$url = add_query_arg( $args, $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_favorites_permalink', $url, $user_id );
+	}
+
+/**
+ * Output the link to make a topic favorite/remove a topic from favorites
+ *
+ * @since 2.0.0 bbPress (r2652)
+ * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+ *
+ * @param array $args See {@link bbp_get_user_favorites_link()}
+ * @param int $user_id Optional. User id
+ * @param bool $wrap Optional. If you want to wrap the link in <span id="favorite-toggle">.
+ */
+function bbp_user_favorites_link( $args = array(), $user_id = 0, $wrap = true ) {
+	echo bbp_get_user_favorites_link( $args, $user_id, $wrap );
+}
+	/**
+	 * User favorites link
+	 *
+	 * Return the link to make a topic favorite/remove a topic from
+	 * favorites
+	 *
+	 * @since 2.0.0 bbPress (r2652)
+	 * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - subscribe: Favorite text
+	 *  - unsubscribe: Unfavorite text
+	 *  - user_id: User id
+	 *  - topic_id: Topic id
+	 *  - before: Before the link
+	 *  - after: After the link
+	 * @param int $user_id Optional. User id
+	 * @param int $topic_id Optional. Topic id
+	 * @param bool $wrap Optional. If you want to wrap the link in <span id="favorite-toggle">. See ajax_favorite()
+	 * @return string User favorites link
+	 */
+	function bbp_get_user_favorites_link( $args = array(), $user_id = 0, $wrap = true ) {
+
+		// Bail if favorites are inactive
+		if ( ! bbp_is_favorites_active() ) {
+			return false;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'favorite'    => esc_html__( 'Favorite',   'bbpress' ),
+			'favorited'   => esc_html__( 'Unfavorite', 'bbpress' ),
+			'user_id'     => 0,
+			'object_id'   => 0,
+			'object_type' => 'post',
+			'before'      => '',
+			'after'       => '',
+			'redirect_to' => '',
+
+			// Deprecated. Use object_id.
+			'forum_id'    => 0,
+			'topic_id'    => 0
+		), 'get_user_favorites_link' );
+
+		// Validate user and object ID's
+		$user_id     = bbp_get_user_id( $r['user_id'], true, true );
+		$object_type = sanitize_key( $r['object_type'] );
+
+		// Back-compat for deprecated arguments
+		if ( ! empty( $r['topic_id'] ) ) {
+			$object_id = absint( $r['topic_id'] );
+		} elseif ( ! empty( $r['forum_id'] ) ) {
+			$object_id = absint( $r['forum_id'] );
+		} else {
+			$object_id = absint( $r['object_id'] );
+		}
+
+		// Bail if empty
+		if ( empty( $user_id ) || empty( $object_id ) || empty( $object_type ) ) {
+			return false;
+		}
+
+		// No link if you can't edit yourself
+		if ( ! current_user_can( 'edit_user', $user_id ) ) {
+			return false;
+		}
+
+		// Decide which link to show
+		$is_fav = bbp_is_user_favorite( $user_id, $object_id );
+		if ( ! empty( $is_fav ) ) {
+			$text   = $r['favorited'];
+			$q_args = array(
+				'action'    => 'bbp_favorite_remove',
+				'object_id' => $object_id
+			);
+		} else {
+			$text   = $r['favorite'];
+			$q_args = array(
+				'action'    => 'bbp_favorite_add',
+				'object_id' => $object_id
+			);
+		}
+
+		// Custom redirect
+		if ( ! empty( $r['redirect_to'] ) ) {
+			$q_args['redirect_to'] = urlencode( $r['redirect_to'] );
+		}
+
+		// URL
+		$url  = esc_url( wp_nonce_url( add_query_arg( $q_args ), 'toggle-favorite_' . $object_id ) );
+		$sub  = $is_fav ? ' class="is-favorite"' : '';
+		$html = sprintf( '%s<span id="favorite-%d"  %s><a href="%s" class="favorite-toggle" data-bbp-object-id="%d" data-bbp-object-type="%s" data-bbp-nonce="%s">%s</a></span>%s', $r['before'], $object_id, $sub, $url, $object_id, $object_type, wp_create_nonce( 'toggle-favorite_' . $object_id ), $text, $r['after'] );
+
+		// Initial output is wrapped in a span, ajax output is hooked to this
+		if ( ! empty( $wrap ) ) {
+			$html = '<span id="favorite-toggle">' . $html . '</span>';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_favorites_link', $html, $r, $user_id, $object_id );
+	}
+
+/** Subscriptions *************************************************************/
+
+/**
+ * Output the link to the user's subscriptions page (profile page)
+ *
+ * @since 2.0.0 bbPress (r2688)
+ * @since 2.6.0 bbPress (r6308) Add pagination if in the loop
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_subscriptions_permalink( $user_id = 0 ) {
+	echo esc_url( bbp_get_subscriptions_permalink( $user_id ) );
+}
+	/**
+	 * Return the link to the user's subscriptions page (profile page)
+	 *
+	 * @since 2.0.0 bbPress (r2688)
+	 * @since 2.6.0 bbPress (r6308) Add pagination if in the loop
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string Permanent link to user subscriptions page
+	 */
+	function bbp_get_subscriptions_permalink( $user_id = 0 ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_subscriptions_permalink', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL
+		$profile_url = bbp_get_user_profile_url( $user_id );
+		$page        = 0;
+		$paged       = false;
+
+		// Get pagination data
+		if ( bbpress()->topic_query->in_the_loop ) {
+			$page  = (int)  bbpress()->topic_query->paged;
+			$paged = (bool) bbpress()->topic_query->in_the_loop;
+
+		} elseif ( bbpress()->forum_query->in_the_loop ) {
+			$page  = (int)  bbpress()->forum_query->paged;
+			$paged = (bool) bbpress()->forum_query->in_the_loop;
+		}
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+
+			// Base URL
+			$url = trailingslashit( $profile_url ) . bbp_get_user_subscriptions_slug();
+
+			// Add page
+			if ( ( true === $paged ) && ( $page > 1 ) ) {
+				$url = trailingslashit( $url ) . bbp_get_paged_slug() . '/' . $page;
+			}
+
+			// Ensure correct trailing slash
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+
+			// Base arguments
+			$args = array(
+				bbp_get_user_subscriptions_rewrite_id() => bbp_get_user_subscriptions_slug(),
+			);
+
+			// Add page
+			if ( ( true === $paged ) && ( $page > 1 ) ) {
+				$args['page'] = $page;
+			}
+
+			// Add arguments
+			$url = add_query_arg( $args, $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_subscriptions_permalink', $url, $user_id );
+	}
+
+/**
+ * Output the link to subscribe/unsubscribe from a topic
+ *
+ * @since 2.0.0 bbPress (r2668)
+ * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+ *
+ * @param array $args See {@link bbp_get_user_subscribe_link()}
+ * @param int $user_id Optional. User id
+ * @param bool $wrap Optional. If you want to wrap the link in <span id="subscription-toggle">.
+ */
+function bbp_user_subscribe_link( $args = array(), $user_id = 0, $wrap = true ) {
+	echo bbp_get_user_subscribe_link( $args, $user_id, $wrap );
+}
+	/**
+	 * Return the link to subscribe/unsubscribe from a forum or topic
+	 *
+	 * @since 2.0.0 bbPress (r2668)
+	 * @since 2.6.0 bbPress (r6308) Add 'redirect_to' support
+	 *
+	 * @param array $args This function supports these arguments:
+	 *  - subscribe: Subscribe text
+	 *  - unsubscribe: Unsubscribe text
+	 *  - user_id: User id
+	 *  - topic_id: Topic id
+	 *  - forum_id: Forum id
+	 *  - before: Before the link
+	 *  - after: After the link
+	 * @param int $user_id Optional. User id
+	 * @param bool $wrap Optional. If you want to wrap the link in <span id="subscription-toggle">.
+	 * @return string Permanent link to topic
+	 */
+	function bbp_get_user_subscribe_link( $args = array(), $user_id = 0, $wrap = true ) {
+
+		// Bail if subscriptions are inactive
+		if ( ! bbp_is_subscriptions_active() ) {
+			return;
+		}
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'subscribe'   => esc_html__( 'Subscribe',   'bbpress' ),
+			'unsubscribe' => esc_html__( 'Unsubscribe', 'bbpress' ),
+			'user_id'     => 0,
+			'object_id'   => 0,
+			'object_type' => 'post',
+			'before'      => '',
+			'after'       => '',
+			'redirect_to' => '',
+
+			// Deprecated. Use object_id.
+			'forum_id'    => 0,
+			'topic_id'    => 0
+		), 'get_user_subscribe_link' );
+
+		// Validate user
+		$user_id     = bbp_get_user_id( $r['user_id'], true, true );
+		$object_type = sanitize_key( $r['object_type'] );
+
+		// Back-compat for deprecated arguments
+		if ( ! empty( $r['topic_id'] ) ) {
+			$object_id = absint( $r['topic_id'] );
+		} elseif ( ! empty( $r['forum_id'] ) ) {
+			$object_id = absint( $r['forum_id'] );
+		} else {
+			$object_id = absint( $r['object_id'] );
+		}
+
+		// Bail if anything is missing
+		if ( empty( $user_id ) || empty( $object_id ) || empty( $object_type ) ) {
+			return false;
+		}
+
+		// No link if you can't edit yourself
+		if ( ! current_user_can( 'edit_user', $user_id ) ) {
+			return false;
+		}
+
+		// Decide which link to show
+		$is_subscribed = bbp_is_user_subscribed( $user_id, $object_id );
+		if ( ! empty( $is_subscribed ) ) {
+			$text   = $r['unsubscribe'];
+			$q_args = array(
+				'action'      => 'bbp_unsubscribe',
+				'object_id'   => $object_id,
+				'object_type' => $object_type
+			);
+		} else {
+			$text   = $r['subscribe'];
+			$q_args = array(
+				'action'      => 'bbp_subscribe',
+				'object_id'   => $object_id,
+				'object_type' => $object_type
+			);
+		}
+
+		// Custom redirect
+		if ( ! empty( $r['redirect_to'] ) ) {
+			$q_args['redirect_to'] = urlencode( $r['redirect_to'] );
+		}
+
+		// URL
+		$url  = esc_url( wp_nonce_url( add_query_arg( $q_args ), 'toggle-subscription_' . $object_id ) );
+		$sub  = $is_subscribed ? ' class="is-subscribed"' : '';
+		$html = sprintf( '%s<span id="subscribe-%d"  %s><a href="%s" class="subscription-toggle" data-bbp-object-id="%d" data-bbp-object-type="%d" data-bbp-nonce="%s">%s</a></span>%s', $r['before'], $object_id, $sub, $url, $object_id, $object_type, wp_create_nonce( 'toggle-subscription_' . $object_id ), $text, $r['after'] );
+
+		// Initial output is wrapped in a span, ajax output is hooked to this
+		if ( ! empty( $wrap ) ) {
+			$html = '<span id="subscription-toggle">' . $html . '</span>';
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_subscribe_link', $html, $r, $user_id, $object_id );
+	}
+
+
+/** Edit User *****************************************************************/
+
+/**
+ * Display profile edit success notice on user edit page
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_notice_edit_user_success() {
+
+	// Bail if no updated argument
+	if ( empty( $_GET['updated'] ) ) {
+		return;
+	}
+
+	// Bail if not on users own profile
+	if ( ! bbp_is_single_user_edit() ) {
+		return;
+	} ?>
+
+	<div class="bbp-template-notice">
+		<ul>
+			<li><?php esc_html_e( 'User updated.', 'bbpress' ); ?></li>
+		</ul>
+	</div>
+
+	<?php
+}
+
+/**
+ * Display pending email change notice on user edit page
+ *
+ * @since 2.6.0 bbPress (r5660)
+ */
+function bbp_notice_edit_user_pending_email() {
+
+	// Bail if not on users own profile
+	if ( ! bbp_is_user_home_edit() ) {
+		return;
+	}
+
+	// Check for pending email address change
+	$user_id   = bbp_get_displayed_user_id();
+	$key       = '_new_email';
+	$new_email = get_user_meta( $user_id, $key, true );
+
+	// Bail if no pending email address change
+	if ( empty( $new_email['newemail'] ) ) {
+		return;
+	}
+
+	// Build the nonced URL to dismiss the pending change
+	$user_url = bbp_get_user_profile_edit_url( $user_id );
+	$nonce    = "dismiss-{$user_id}{$key}";
+	$args     = array(
+		'action'  => 'bbp-update-user-email',
+		'dismiss' => "{$user_id}{$key}"
+	);
+
+	// Build the variables to pass into printf()
+	$dismiss_url  = wp_nonce_url( add_query_arg( $args, $user_url ), $nonce );
+	$dismiss_link = '<a href="' . esc_url( $dismiss_url ) . '">' . esc_html_x( 'Cancel', 'Dismiss pending user email address change', 'bbpress' ) . '</a>';
+	$coded_email  = '<code>' . esc_html( $new_email['newemail'] ) . '</code>'; ?>
+
+	<div class="bbp-template-notice info">
+		<ul>
+			<li><?php printf( esc_html__( 'There is a pending email address change to %1$s. %2$s', 'bbpress' ), $coded_email, $dismiss_link ); ?></li>
+		</ul>
+	</div>
+
+	<?php
+}
+
+/**
+ * Super admin privileges notice
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_notice_edit_user_is_super_admin() {
+	if ( is_multisite() && ( bbp_is_single_user() || bbp_is_single_user_edit() ) && current_user_can( 'manage_network_options' ) && is_super_admin( bbp_get_displayed_user_id() ) ) : ?>
+
+	<div class="bbp-template-notice important">
+		<ul>
+			<li><?php bbp_is_user_home() || bbp_is_user_home_edit() ? esc_html_e( 'You have super admin privileges.', 'bbpress' ) : esc_html_e( 'This user has super admin privileges.', 'bbpress' ); ?></li>
+		</ul>
+	</div>
+
+<?php endif;
+}
+
+/**
+ * Drop down for selecting the user's display name
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_edit_user_display_name() {
+	$bbp            = bbpress();
+	$public_display = array();
+	$public_display['display_username'] = $bbp->displayed_user->user_login;
+
+	if ( ! empty( $bbp->displayed_user->nickname ) ) {
+		$public_display['display_nickname']  = $bbp->displayed_user->nickname;
+	}
+
+	if ( ! empty( $bbp->displayed_user->first_name ) ) {
+		$public_display['display_firstname'] = $bbp->displayed_user->first_name;
+	}
+
+	if ( ! empty( $bbp->displayed_user->last_name ) ) {
+		$public_display['display_lastname']  = $bbp->displayed_user->last_name;
+	}
+
+	if ( ! empty( $bbp->displayed_user->first_name ) && ! empty( $bbp->displayed_user->last_name ) ) {
+		$public_display['display_firstlast'] = $bbp->displayed_user->first_name . ' ' . $bbp->displayed_user->last_name;
+		$public_display['display_lastfirst'] = $bbp->displayed_user->last_name  . ' ' . $bbp->displayed_user->first_name;
+	}
+
+	// Only add this if it isn't duplicated elsewhere
+	if ( ! in_array( $bbp->displayed_user->display_name, $public_display, true ) ) {
+		$public_display = array( 'display_displayname' => $bbp->displayed_user->display_name ) + $public_display;
+	}
+
+	$public_display = array_map( 'trim', $public_display );
+	$public_display = array_unique( $public_display ); ?>
+
+	<select name="display_name" id="display_name">
+
+	<?php foreach ( $public_display as $id => $item ) : ?>
+
+		<option id="<?php echo $id; ?>" value="<?php echo esc_attr( $item ); ?>"<?php selected( $bbp->displayed_user->display_name, $item ); ?>><?php echo $item; ?></option>
+
+	<?php endforeach; ?>
+
+	</select>
+
+<?php
+}
+
+/**
+ * Output blog role selector (for user edit)
+ *
+ * @since 2.0.0 bbPress (r2688)
+ */
+function bbp_edit_user_blog_role() {
+
+	// Bail if no user is being edited
+	if ( ! bbp_is_single_user_edit() ) {
+		return;
+	}
+
+	// Get users current blog role
+	$user_role  = bbp_get_user_blog_role( bbp_get_displayed_user_id() );
+
+	// Get the blog roles
+	$blog_roles = bbp_get_blog_roles(); ?>
+
+	<select name="role" id="role">
+		<option value=""><?php esc_html_e( '&mdash; No role for this site &mdash;', 'bbpress' ); ?></option>
+
+		<?php foreach ( $blog_roles as $role => $details ) : ?>
+
+			<option <?php selected( $user_role, $role ); ?> value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+
+		<?php endforeach; ?>
+
+	</select>
+
+	<?php
+}
+
+/**
+ * Output forum role selector (for user edit)
+ *
+ * @since 2.2.0 bbPress (r4284)
+ */
+function bbp_edit_user_forums_role() {
+
+	// Bail if no user is being edited
+	if ( ! bbp_is_single_user_edit() ) {
+		return;
+	}
+
+	// Get the user's current forum role
+	$user_role     = bbp_get_user_role( bbp_get_displayed_user_id() );
+
+	// Get the folum roles
+	$dynamic_roles = bbp_get_dynamic_roles();
+
+	// Only keymasters can set other keymasters
+	if ( ! bbp_is_user_keymaster() ) {
+		unset( $dynamic_roles[ bbp_get_keymaster_role() ] );
+	} ?>
+
+	<select name="bbp-forums-role" id="bbp-forums-role">
+		<option value=""><?php esc_html_e( '&mdash; No role for these forums &mdash;', 'bbpress' ); ?></option>
+
+		<?php foreach ( $dynamic_roles as $role => $details ) : ?>
+
+			<option <?php selected( $user_role, $role ); ?> value="<?php echo esc_attr( $role ); ?>"><?php echo bbp_translate_user_role( $details['name'] ); ?></option>
+
+		<?php endforeach; ?>
+
+	</select>
+
+	<?php
+}
+
+/**
+ * Return user contact methods select box
+ *
+ * @since 2.0.0 bbPress (r2688)
+ * @return string User contact methods
+ */
+function bbp_edit_user_contact_methods() {
+
+	// Get the core WordPress contact methods
+	$contact_methods = wp_get_user_contact_methods( bbpress()->displayed_user );
+
+	// Filter & return
+	return (array) apply_filters( 'bbp_edit_user_contact_methods', $contact_methods );
+}
+
+/**
+ * Output the language chooser (for user edit)
+ *
+ * @since 2.6.0 bbPress (r6488)
+ *
+ * @param array $args See wp_dropdown_languages()
+ * @return string
+ */
+function bbp_edit_user_language( $args = array() ) {
+
+	// Bail if no user is being edited
+	if ( ! bbp_is_single_user_edit() ) {
+		return;
+	}
+
+	// Output the dropdown
+	bbp_user_languages_dropdown( $args );
+}
+
+/** Topics Created ************************************************************/
+
+/**
+ * Output the link to the user's topics
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_user_topics_created_url( $user_id = 0 ) {
+	echo esc_url( bbp_get_user_topics_created_url( $user_id ) );
+}
+	/**
+	 * Return the link to the user's topics
+	 *
+	 * @since 2.2.0 bbPress (r4225)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string Permanent link to user profile page
+	 */
+	function bbp_get_user_topics_created_url( $user_id = 0 ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_user_topics_created_url', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL
+		$profile_url = bbp_get_user_profile_url( $user_id );
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+			$url = trailingslashit( $profile_url ) . bbp_get_topic_archive_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_user_topics_rewrite_id() => '1',
+			), $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_topics_created_url', $url, $user_id );
+	}
+
+/** Replies Created ************************************************************/
+
+/**
+ * Output the link to the user's replies
+ *
+ * @since 2.2.0 bbPress (r4225)
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_user_replies_created_url( $user_id = 0 ) {
+	echo esc_url( bbp_get_user_replies_created_url( $user_id ) );
+}
+	/**
+	 * Return the link to the user's replies
+	 *
+	 * @since 2.2.0 bbPress (r4225)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string Permanent link to user profile page
+	 */
+	function bbp_get_user_replies_created_url( $user_id = 0 ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_user_replies_created_url', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL
+		$profile_url = bbp_get_user_profile_url( $user_id );
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+			$url = trailingslashit( $profile_url ) . bbp_get_reply_archive_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_user_replies_rewrite_id() => '1',
+			), $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_replies_created_url', $url, $user_id );
+	}
+
+/** Engagements ***************************************************************/
+
+/**
+ * Output the link to the user's engagements
+ *
+ * @since 2.6.0 bbPress (r6320)
+ *
+ * @param int $user_id Optional. User id
+ */
+function bbp_user_engagements_url( $user_id = 0 ) {
+	echo esc_url( bbp_get_user_engagements_url( $user_id ) );
+}
+	/**
+	 * Return the link to the user's engagements
+	 *
+	 * @since 2.6.0 bbPress (r6320)
+	 *
+	 * @param int $user_id Optional. User id
+	 * @return string Permanent link to user profile page
+	 */
+	function bbp_get_user_engagements_url( $user_id = 0 ) {
+
+		// Use displayed user ID if there is one, and one isn't requested
+		$user_id = bbp_get_user_id( $user_id );
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		// Bail if intercepted
+		$intercept = bbp_maybe_intercept( 'bbp_pre_get_user_engagements_url', func_get_args() );
+		if ( bbp_is_intercepted( $intercept ) ) {
+			return $intercept;
+		}
+
+		// Get user profile URL
+		$profile_url = bbp_get_user_profile_url( $user_id );
+
+		// Pretty permalinks
+		if ( bbp_use_pretty_urls() ) {
+			$url = trailingslashit( $profile_url ) . bbp_get_user_engagements_slug();
+			$url = user_trailingslashit( $url );
+
+		// Unpretty permalinks
+		} else {
+			$url = add_query_arg( array(
+				bbp_get_user_engagements_rewrite_id() => '1',
+			), $profile_url );
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_engagements_url', $url, $user_id );
+	}
+
+/** Language ******************************************************************/
+
+/**
+ * Output the select element used to save a user's language
+ *
+ * @since 2.6.0 bbPress (r6488)
+ *
+ * @param array $args See wp_dropdown_languages()
+ */
+function bbp_user_languages_dropdown( $args = array() ) {
+	echo bbp_get_user_languages_dropdown( $args );
+}
+
+	/**
+	 * Return the select element used to save a user's language.
+	 *
+	 * @since 2.6.0 bbPress (r6488)
+	 *
+	 * @param array $args See wp_dropdown_languages()
+	 * @return string
+	 */
+	function bbp_get_user_languages_dropdown( $args = array() ) {
+
+		// Get user language
+		$user_id = ! empty( $args['user_id'] )
+			? bbp_get_user_id( $args['user_id'], false, false )
+			: bbp_get_displayed_user_id();
+
+		// Get user locale
+		$user_locale = ! empty( $user_id )
+			? get_userdata( $user_id )->locale
+			: 'site-default';
+
+		// Get all languages
+		$languages = get_available_languages();
+
+		// No locale for English
+		if ( 'en_US' === $user_locale ) {
+			$user_locale = '';
+
+		// Fallback to site-default if there is a mismatch
+		} elseif ( '' === $user_locale || ! in_array( $user_locale, $languages, true ) ) {
+			$user_locale = 'site-default';
+		}
+
+		// Don't pass user ID in
+		unset( $args['user_id'] );
+
+		// Parse arguments
+		$r = bbp_parse_args( $args, array(
+			'name'                        => 'locale',
+			'id'                          => 'locale',
+			'selected'                    => $user_locale,
+			'languages'                   => $languages,
+			'echo'                        => false,
+			'show_available_translations' => false,
+			'show_option_site_default'    => true
+		), 'user_languages_dropdown' );
+
+		// Get the markup for the languages drop-down
+		$retval = wp_dropdown_languages( $r );
+
+		// Filter & return
+		return apply_filters( 'bbp_get_user_languages_dropdown', $retval, $r, $args );
+	}
+
+/** Login *********************************************************************/
+
+/**
+ * Handle the login and registration template notices
+ *
+ * @since 2.0.0 bbPress (r2970)
+ */
+function bbp_login_notices() {
+
+	// loggedout was passed
+	if ( ! empty( $_GET['loggedout'] ) && ( true === $_GET['loggedout'] ) ) {
+		bbp_add_error( 'loggedout', esc_html__( 'You are now logged out.', 'bbpress' ), 'message' );
+
+	// registration is disabled
+	} elseif ( ! empty( $_GET['registration'] ) && ( 'disabled' === $_GET['registration'] ) ) {
+		bbp_add_error( 'registerdisabled', esc_html__( 'New user registration is currently not allowed.', 'bbpress' ) );
+
+	// Prompt user to check their email
+	} elseif ( ! empty( $_GET['checkemail'] ) && in_array( $_GET['checkemail'], array( 'confirm', 'newpass', 'registered' ), true ) ) {
+
+		switch ( $_GET['checkemail'] ) {
+
+			// Email needs confirmation
+			case 'confirm' :
+				bbp_add_error( 'confirm',    esc_html__( 'Check your e-mail for the confirmation link.',     'bbpress' ), 'message' );
+				break;
+
+			// User requested a new password
+			case 'newpass' :
+				bbp_add_error( 'newpass',    esc_html__( 'Check your e-mail for your new password.',         'bbpress' ), 'message' );
+				break;
+
+			// User is newly registered
+			case 'registered' :
+				bbp_add_error( 'registered', esc_html__( 'Registration complete. Please check your e-mail.', 'bbpress' ), 'message' );
+				break;
+		}
+	}
+}
+
+/**
+ * Redirect a user back to their profile if they are already logged in.
+ *
+ * This should be used before {@link get_header()} is called in template files
+ * where the user should never have access to the contents of that file.
+ *
+ * @since 2.0.0 bbPress (r2815)
+ *
+ * @param string $url The URL to redirect to
+ */
+function bbp_logged_in_redirect( $url = '' ) {
+
+	// Bail if user is not logged in
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	// Setup the profile page to redirect to
+	$redirect_to = ! empty( $url ) ? $url : bbp_get_user_profile_url( bbp_get_current_user_id() );
+
+	// Do a safe redirect
+	bbp_redirect( $redirect_to );
+}
+
+/**
+ * Output the required hidden fields when logging in
+ *
+ * @since 2.0.0 bbPress (r2815)
+ */
+function bbp_user_login_fields() {
+?>
+
+	<input type="hidden" name="user-cookie" value="1" />
+
+	<?php
+
+	// Allow custom login redirection
+	$redirect_to = apply_filters( 'bbp_user_login_redirect_to', '' );
+	bbp_redirect_to_field( $redirect_to );
+
+	// Prevent intention hi-jacking of log-in form
+	wp_nonce_field( 'bbp-user-login' );
+}
+
+/** Register ******************************************************************/
+
+/**
+ * Output the required hidden fields when registering
+ *
+ * @since 2.0.0 bbPress (r2815)
+ */
+function bbp_user_register_fields() {
+?>
+
+	<input type="hidden" name="action"      value="register" />
+	<input type="hidden" name="user-cookie" value="1" />
+
+	<?php
+
+	// Allow custom registration redirection
+	$redirect_to = apply_filters( 'bbp_user_register_redirect_to', '' );
+	bbp_redirect_to_field( add_query_arg( array( 'checkemail' => 'registered' ), $redirect_to ) );
+
+	// Prevent intention hi-jacking of sign-up form
+	wp_nonce_field( 'bbp-user-register' );
+}
+
+/** Lost Password *************************************************************/
+
+/**
+ * Output the required hidden fields when user lost password
+ *
+ * @since 2.0.0 bbPress (r2815)
+ */
+function bbp_user_lost_pass_fields() {
+?>
+
+	<input type="hidden" name="user-cookie" value="1" />
+
+	<?php
+
+	// Allow custom lost pass redirection
+	$redirect_to = apply_filters( 'bbp_user_lost_pass_redirect_to', get_permalink() );
+	bbp_redirect_to_field( add_query_arg( array( 'checkemail' => 'confirm' ), $redirect_to ) );
+
+	// Prevent intention hi-jacking of lost pass form
+	wp_nonce_field( 'bbp-user-lost-pass' );
+}
+
+/** Author Avatar *************************************************************/
+
+/**
+ * Output the author link of a post
+ *
+ * @since 2.0.0 bbPress (r2875)
+ *
+ * @param array $args Optional. If it is an integer, it is used as post id.
+ */
+function bbp_author_link( $args = array() ) {
+	echo bbp_get_author_link( $args );
+}
+	/**
+	 * Return the author link of the post
+	 *
+	 * @since 2.0.0 bbPress (r2875)
+	 *
+	 * @param array $args Optional. If an integer, it is used as reply id.
+	 * @return string Author link of reply
+	 */
+	function bbp_get_author_link( $args = array() ) {
+
+		$post_id = is_numeric( $args ) ? (int) $args : 0;
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'post_id'    => $post_id,
+			'link_title' => '',
+			'type'       => 'both',
+			'size'       => 80,
+			'sep'        => ''
+		), 'get_author_link' );
+
+		// Confirmed topic
+		if ( bbp_is_topic( $r['post_id'] ) ) {
+			return bbp_get_topic_author_link( $r );
+
+		// Confirmed reply
+		} elseif ( bbp_is_reply( $r['post_id'] ) ) {
+			return bbp_get_reply_author_link( $r );
+		}
+
+		// Default return value
+		$author_link = '';
+
+		// Neither a reply nor a topic, so could be a revision
+		if ( ! empty( $r['post_id'] ) ) {
+
+			// Get some useful reply information
+			$user_id    = get_post_field( 'post_author', $r['post_id'] );
+			$author_url = bbp_get_user_profile_url( $user_id );
+			$anonymous  = bbp_is_reply_anonymous( $r['post_id'] );
+
+			// Generate title with the display name of the author
+			if ( empty( $r['link_title'] ) ) {
+				$author = get_the_author_meta( 'display_name', $user_id );
+				$title  = empty( $anonymous )
+					? esc_attr__( "View %s's profile",  'bbpress' )
+					: esc_attr__( "Visit %s's website", 'bbpress' );
+
+				$r['link_title'] = sprintf( $title, $author );
+			}
+
+			// Setup title and author_links array
+			$author_links = array();
+			$link_title   = ! empty( $r['link_title'] )
+				? ' title="' . esc_attr( $r['link_title'] ) . '"'
+				: '';
+
+			// Get avatar (unescaped, because HTML)
+			if ( ( 'avatar' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['avatar'] = get_avatar( $user_id, $r['size'] );
+			}
+
+			// Get display name (escaped, because never HTML)
+			if ( ( 'name' === $r['type'] ) || ( 'both' === $r['type'] ) ) {
+				$author_links['name'] = esc_html( get_the_author_meta( 'display_name', $user_id ) );
+			}
+
+			// Empty array
+			$links  = array();
+			$sprint = '<span %1$s>%2$s</span>';
+
+			// Wrap each link
+			foreach ( $author_links as $link => $link_text ) {
+				$link_class = ' class="bbp-author-' . esc_attr( $link ) . '"';
+				$links[]    = sprintf( $sprint, $link_class, $link_text );
+			}
+
+			// Juggle
+			$author_links = $links;
+			unset( $links );
+
+			// Filter sections
+			$sections    = apply_filters( 'bbp_get_author_links', $author_links, $r, $args );
+
+			// Assemble sections into author link
+			$author_link = implode( $r['sep'], $sections );
+
+			// Only wrap in link if profile exists
+			if ( empty( $anonymous ) && bbp_user_has_profile( $user_id ) ) {
+				$author_link = sprintf( '<a href="%1$s"%2$s%3$s>%4$s</a>', esc_url( $author_url ), $link_title, ' class="bbp-author-link"', $author_link );
+			}
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_author_link', $author_link, $r, $args );
+	}
+
+/** Capabilities **************************************************************/
+
+/**
+ * Check if the user can access a specific forum
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @return bool
+ */
+function bbp_user_can_view_forum( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'user_id'         => bbp_get_current_user_id(),
+		'forum_id'        => bbp_get_forum_id(),
+		'check_ancestors' => false
+	), 'user_can_view_forum' );
+
+	// Validate parsed values
+	$user_id  = bbp_get_user_id( $r['user_id'], false, false );
+	$forum_id = bbp_get_forum_id( $r['forum_id'] );
+	$retval   = false;
+
+	// User is a keymaster
+	if ( ! empty( $user_id ) && bbp_is_user_keymaster( $user_id ) ) {
+		$retval = true;
+
+	// Forum is public, and user can read forums or is not logged in
+	} elseif ( bbp_is_forum_public( $forum_id, $r['check_ancestors'] ) ) {
+		$retval = true;
+
+	// Forum is private, and user can see it
+	} elseif ( bbp_is_forum_private( $forum_id, $r['check_ancestors'] ) && user_can( $user_id, 'read_forum', $forum_id ) ) {
+		$retval = true;
+
+	// Forum is hidden, and user can see it
+	} elseif ( bbp_is_forum_hidden( $forum_id, $r['check_ancestors'] ) && user_can( $user_id, 'read_forum', $forum_id  ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_user_can_view_forum', $retval, $forum_id, $user_id );
+}
+
+/**
+ * Check if the current user can publish topics
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_publish_topics() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// Do not allow anonymous if not enabled
+	} elseif ( ! is_user_logged_in() && bbp_allow_anonymous() ) {
+		$retval = true;
+
+	// User is logged in
+	} elseif ( current_user_can( 'publish_topics' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_publish_topics', $retval );
+}
+
+/**
+ * Check if the current user can publish forums
+ *
+ * @since 2.1.0 bbPress (r3549)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_publish_forums() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// User is logged in
+	} elseif ( current_user_can( 'publish_forums' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_publish_forums', $retval );
+}
+
+/**
+ * Check if the current user can publish replies
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_publish_replies() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// Do not allow anonymous if not enabled
+	} elseif ( ! is_user_logged_in() && bbp_allow_anonymous() ) {
+		$retval = true;
+
+	// User is logged in
+	} elseif ( current_user_can( 'publish_replies' ) ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_publish_replies', $retval );
+}
+
+/** Forms *********************************************************************/
+
+/**
+ * The following functions should be turned into mapped meta capabilities in a
+ * future version. They exist only to remove complex logistical capability
+ * checks from within template parts.
+ */
+
+/**
+ * Get the forums the current user has the ability to see and post to
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @param array $args
+ *
+ * @return array
+ */
+function bbp_get_forums_for_current_user( $args = array() ) {
+
+	// Parse arguments against default values
+	$r = bbp_parse_args( $args, array(
+		'post_type'    => bbp_get_forum_post_type(),
+		'post_status'  => bbp_get_public_status_id(),
+		'post__not_in' => bbp_exclude_forum_ids( 'array' ),
+		'numberposts'  => -1
+	), 'get_forums_for_current_user' );
+
+	// Get the forums
+	$forums = get_posts( $r );
+
+	// No availabe forums
+	if ( empty( $forums ) ) {
+		$forums = false;
+	}
+
+	// Filter & return
+	return apply_filters( 'bbp_get_forums_for_current_user', $forums, $r, $args );
+}
+
+/**
+ * Performs a series of checks to ensure the current user can create forums.
+ *
+ * @since 2.1.0 bbPress (r3549)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_access_create_forum_form() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// Looking at a single forum & forum is open
+	} elseif ( ( is_page() || is_single() ) && bbp_is_forum_open() ) {
+		$retval = bbp_current_user_can_publish_forums();
+
+	// User can edit this forum
+	} else {
+		$retval = current_user_can( 'edit_forum', bbp_get_forum_id() );
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_access_create_forum_form', (bool) $retval );
+}
+
+/**
+ * Performs a series of checks to ensure the current user can create topics.
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_access_create_topic_form() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// Looking at a single forum & forum is open
+	} elseif ( ( bbp_is_single_forum() || is_page() || is_single() ) && bbp_is_forum_open() ) {
+		$retval = bbp_current_user_can_publish_topics();
+
+	// User can edit this topic
+	} else {
+		$retval = current_user_can( 'edit_topic', bbp_get_topic_id() );
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_access_create_topic_form', (bool) $retval );
+}
+
+/**
+ * Performs a series of checks to ensure the current user can create replies.
+ *
+ * @since 2.0.0 bbPress (r3127)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_access_create_reply_form() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// Always allow keymasters
+	if ( bbp_is_user_keymaster() ) {
+		$retval = true;
+
+	// Looking at a single topic, topic is open, and forum is open
+	} elseif ( ( bbp_is_single_topic() || is_page() || is_single() ) && bbp_is_topic_open() && bbp_is_forum_open() && bbp_is_topic_published() ) {
+		$retval = bbp_current_user_can_publish_replies();
+
+	// User can edit this reply
+	} elseif ( bbp_get_reply_id() ) {
+		$retval = current_user_can( 'edit_reply', bbp_get_reply_id() );
+
+	// User can edit this topic
+	} elseif ( bbp_get_topic_id() ) {
+		$retval = current_user_can( 'edit_topic', bbp_get_topic_id() );
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_access_create_reply_form', (bool) $retval );
+}
+
+/**
+ * Performs a series of checks to ensure the current user should see the
+ * anonymous user form fields.
+ *
+ * @since 2.5.0 bbPress (r5119)
+ *
+ * @return bool
+ */
+function bbp_current_user_can_access_anonymous_user_form() {
+
+	// Users need to earn access
+	$retval = false;
+
+	// User is not logged in, and anonymous posting is allowed
+	if ( bbp_is_anonymous() ) {
+		$retval = true;
+
+	// User is editing a topic, and topic is authored by anonymous user
+	} elseif ( bbp_is_topic_edit() && bbp_is_topic_anonymous() ) {
+		$retval = true;
+
+	// User is editing a reply, and reply is authored by anonymous user
+	} elseif ( bbp_is_reply_edit() && bbp_is_reply_anonymous() ) {
+		$retval = true;
+	}
+
+	// Filter & return
+	return (bool) apply_filters( 'bbp_current_user_can_access_anonymous_user_form', (bool) $retval );
+}
+
+/** Moderators ****************************************************************/
+
+/**
+ * Output the moderators of a forum
+ *
+ * @since 2.6.0 bbPress
+ *
+ * @param int   $forum_id Optional. Topic id
+ * @param array $args     See {@link bbp_get_moderator_list()}
+ */
+function bbp_moderator_list( $forum_id = 0, $args = array() ) {
+	echo bbp_get_moderator_list( $forum_id, $args );
+}
+
+	/**
+	 * Return the moderators for an object
+	 *
+	 * @since 2.6.0 bbPress
+	 *
+	 * @param int   $object_id Optional. Object id
+	 * @param array $args     This function supports these arguments:
+	 *  - before: Before the tag list
+	 *  - sep: Tag separator
+	 *  - after: After the tag list
+	 *
+	 * @return string Moderator list of the object
+	 */
+	function bbp_get_moderator_list( $object_id = 0, $args = array() ) {
+
+		// Parse arguments against default values
+		$r = bbp_parse_args( $args, array(
+			'before' => '<div class="bbp-moderators"><p>' . esc_html__( 'Moderators:', 'bbpress' ) . '&nbsp;',
+			'sep'    => ', ',
+			'after'  => '</p></div>',
+			'none'   => ''
+		), 'get_moderator_list' );
+
+		// Get forum moderators
+		$user_ids = bbp_get_moderator_ids( $object_id );
+		if ( ! empty( $user_ids ) ) {
+
+			// In admin, use nicenames
+			if ( is_admin() ) {
+				$users = bbp_get_user_nicenames_from_ids( $user_ids );
+
+			// In theme, use display names & profile links
+			} else {
+				foreach ( $user_ids as $user_id ) {
+					$users[] = bbp_get_user_profile_link( $user_id );
+				}
+			}
+
+			$retval = $r['before'] . implode( $r['sep'], $users ) . $r['after'];
+
+		// No forum moderators
+		} else {
+			$retval = $r['none'];
+		}
+
+		// Filter & return
+		return apply_filters( 'bbp_get_moderator_list', $retval );
+	}
Index: /tags/2.6.6/src/index.php
===================================================================
--- /tags/2.6.6/src/index.php	(revision 7162)
+++ /tags/2.6.6/src/index.php	(revision 7162)
@@ -0,0 +1,5 @@
+<?php
+
+/**
+ * Do not modify the files in this folder.
+ */
Index: /tags/2.6.6/src/license.txt
===================================================================
--- /tags/2.6.6/src/license.txt	(revision 7162)
+++ /tags/2.6.6/src/license.txt	(revision 7162)
@@ -0,0 +1,281 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+              51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
Index: /tags/2.6.6/src/readme.txt
===================================================================
--- /tags/2.6.6/src/readme.txt	(revision 7162)
+++ /tags/2.6.6/src/readme.txt	(revision 7162)
@@ -0,0 +1,58 @@
+=== bbPress ===
+Contributors: matt, johnjamesjacoby, jmdodd, netweb, sergeybiryukov
+Tags: forum, forums, discussion, support
+Tested up to: 5.6
+Stable tag: 2.6.6
+License: GPLv2 or later
+License URI: https://www.gnu.org/licenses/gpl-2.0.html
+Requires PHP: 5.6.20
+Requires at least: 5.0
+
+bbPress is forum software for WordPress.
+
+== Description ==
+
+Are you looking for a timeless, elegant, and streamlined discussion board? bbPress is easy to integrate, easy to use, and is built to scale with your growing community.
+
+bbPress is intentionally simple yet infinitely powerful forum software, built by contributors to WordPress.
+
+== Installation ==
+
+= From your WordPress dashboard =
+
+1. Visit 'Plugins > Add New'
+2. Search for 'bbPress'
+3. Activate bbPress from your Plugins page. (You will be greeted with a Welcome page.)
+
+= From WordPress.org =
+
+1. Download bbPress.
+2. Upload the 'bbpress' directory to your '/wp-content/plugins/' directory, using your favorite method (ftp, sftp, scp, etc...)
+3. Activate bbPress from your Plugins page. (You will be greeted with a Welcome page.)
+
+= Once Activated =
+
+1. Visit 'Forums > Add New' and create some forums. (You can always delete these later.)
+2. If you have pretty permalinks enabled, visit example.com/forums/, or if you do not have pretty permalinks enabled, visit example.com?post_type=forum
+3. Visit 'Settings > Forums' and configure the settings to best match the needs of your community.
+4. Visit 'Tools > Forums > Import Forums' if you have an existing forum to convert to bbPress.
+
+= Once Configured =
+
+* bbPress comes with a robust theme-compatibility API that does its best to make bbPress look and feel right with just-about any WordPress theme. You may need to adjust some styling on your own to make everything look pristine.
+* You may want to customize the register/activation/sign-in/lost-password flows, to better suit your site. bbPress comes with a bevy of shortcodes to make this possible, listed here: https://codex.bbpress.org/shortcodes/
+* bbPress also comes with built-in support for Akismet and BuddyPress, two very popular and very powerful WordPress plugins. If you're using either, visit your Forum Settings page and ensure that integration appears correct.
+
+== Screenshots ==
+
+1. Forums - Admin Interface
+2. Topics - Admin Interface
+3. Replies - Admin Interface
+4. Settings - Admin Interface
+5. Settings 2 - Admin Interface
+6. Themes - Admin Interface
+7. Single Forum - Default Theme
+
+== Changelog ==
+
+Check out the [releases page](https://codex.bbpress.org/releases/)
Index: /tags/2.6.6/src/templates/default/bbpress-functions.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress-functions.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress-functions.php	(revision 7162)
@@ -0,0 +1,356 @@
+<?php
+
+/**
+ * Functions of the Default template-pack
+ *
+ * @package bbPress
+ * @subpackage BBP_Theme_Compat
+ * @since 2.1.0 bbPress (r3732)
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Theme Setup ***************************************************************/
+
+if ( ! class_exists( 'BBP_Default' ) ) :
+
+/**
+ * Loads bbPress Default Theme functionality
+ *
+ * This is not a real theme by WordPress standards, and is instead used as the
+ * fallback for any WordPress theme that does not have bbPress templates in it.
+ *
+ * To make your custom theme bbPress compatible and customize the templates, you
+ * can copy these files into your theme without needing to merge anything
+ * together; bbPress should safely handle the rest.
+ *
+ * See @link BBP_Theme_Compat() for more.
+ *
+ * @since 2.1.0 bbPress (r3732)
+ *
+ * @package bbPress
+ * @subpackage BBP_Theme_Compat
+ */
+class BBP_Default extends BBP_Theme_Compat {
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main bbPress (Default) Loader
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function __construct( $properties = array() ) {
+
+		parent::__construct( bbp_parse_args( $properties, array(
+			'id'      => 'default',
+			'name'    => 'bbPress Default',
+			'version' => bbp_get_version(),
+			'dir'     => trailingslashit( bbpress()->themes_dir . 'default' ),
+			'url'     => trailingslashit( bbpress()->themes_url . 'default' ),
+		), 'default_theme' ) );
+
+		$this->setup_actions();
+	}
+
+	/**
+	 * Setup the theme hooks
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 *
+	 * @access private
+	 */
+	private function setup_actions() {
+
+		/** Scripts ***********************************************************/
+
+		add_action( 'bbp_enqueue_scripts',         array( $this, 'enqueue_styles'        ) ); // Enqueue theme CSS
+		add_action( 'bbp_enqueue_scripts',         array( $this, 'enqueue_scripts'       ) ); // Enqueue theme JS
+		add_filter( 'bbp_enqueue_scripts',         array( $this, 'localize_topic_script' ) ); // Enqueue theme script localization
+		add_action( 'bbp_ajax_favorite',           array( $this, 'ajax_favorite'         ) ); // Handles the topic ajax favorite/unfavorite
+		add_action( 'bbp_ajax_subscription',       array( $this, 'ajax_subscription'     ) ); // Handles the topic ajax subscribe/unsubscribe
+
+		/** Template Wrappers *************************************************/
+
+		add_action( 'bbp_before_main_content',  array( $this, 'before_main_content'   ) ); // Top wrapper HTML
+		add_action( 'bbp_after_main_content',   array( $this, 'after_main_content'    ) ); // Bottom wrapper HTML
+
+		/** Override **********************************************************/
+
+		do_action_ref_array( 'bbp_theme_compat_actions', array( &$this ) );
+	}
+
+	/**
+	 * Inserts HTML at the top of the main content area to be compatible with
+	 * the Twenty Twelve theme.
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function before_main_content() {
+	?>
+
+		<div id="bbp-container">
+			<div id="bbp-content" role="main">
+
+	<?php
+	}
+
+	/**
+	 * Inserts HTML at the bottom of the main content area to be compatible with
+	 * the Twenty Twelve theme.
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function after_main_content() {
+	?>
+
+			</div><!-- #bbp-content -->
+		</div><!-- #bbp-container -->
+
+	<?php
+	}
+
+	/**
+	 * Load the theme CSS
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function enqueue_styles() {
+
+		// Setup the default styling
+		$defaults = array(
+			'bbp-default' => array(
+				'file'         => 'css/bbpress.css',
+				'dependencies' => array()
+			)
+		);
+
+		// Optionally support an RTL variant
+		if ( is_rtl() ) {
+			$defaults['bbp-default-rtl'] = array(
+				'file'         => 'css/bbpress-rtl.css',
+				'dependencies' => array()
+			);
+		}
+
+		// Get and filter the bbp-default style
+		$styles = apply_filters( 'bbp_default_styles', $defaults );
+
+		// Enqueue the styles
+		foreach ( $styles as $handle => $attributes ) {
+			bbp_enqueue_style( $handle, $attributes['file'], $attributes['dependencies'], $this->version );
+		}
+	}
+
+	/**
+	 * Enqueue the required JavaScript files
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function enqueue_scripts() {
+
+		// Setup scripts array
+		$scripts = array();
+
+		// Editor scripts
+		// @see https://bbpress.trac.wordpress.org/ticket/2930
+		if ( bbp_use_wp_editor() && is_bbpress() ) {
+			$scripts['bbpress-editor'] = array(
+				'file'         => 'js/editor.js',
+				'dependencies' => array( 'jquery' )
+			);
+		}
+
+		// Forum-specific scripts
+		if ( bbp_is_single_forum() ) {
+			$scripts['bbpress-engagements'] = array(
+				'file'         => 'js/engagements.js',
+				'dependencies' => array( 'jquery' )
+			);
+		}
+
+		// Topic-specific scripts
+		if ( bbp_is_single_topic() || bbp_is_topic_edit() ) {
+
+			// Engagements
+			$scripts['bbpress-engagements'] = array(
+				'file'         => 'js/engagements.js',
+				'dependencies' => array( 'jquery' )
+			);
+
+			// Hierarchical replies
+			if ( bbp_thread_replies() ) {
+				$scripts['bbpress-reply'] = array(
+					'file'         => 'js/reply.js',
+					'dependencies' => array( 'jquery' )
+				);
+			}
+		}
+
+		// User Profile edit
+		if ( bbp_is_single_user_edit() ) {
+			wp_enqueue_script( 'user-profile' );
+		}
+
+		// Filter the scripts
+		$scripts = apply_filters( 'bbp_default_scripts', $scripts );
+
+		// Enqueue the scripts
+		foreach ( $scripts as $handle => $attributes ) {
+			bbp_enqueue_script( $handle, $attributes['file'], $attributes['dependencies'], $this->version, true );
+		}
+	}
+
+	/**
+	 * Load localizations for topic script
+	 *
+	 * These localizations require information that may not be loaded even by init.
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function localize_topic_script() {
+
+		// Single forum or topic
+		if ( bbp_is_single_forum() || bbp_is_single_topic() ) {
+			wp_localize_script( 'bbpress-engagements', 'bbpEngagementJS', array(
+				'object_id'          => get_the_ID(),
+				'bbp_ajaxurl'        => bbp_get_ajax_url(),
+				'generic_ajax_error' => esc_html__( 'Something went wrong. Refresh your browser and try again.', 'bbpress' ),
+			) );
+		}
+	}
+
+	/**
+	 * AJAX handler to add or remove a topic from a user's favorites
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function ajax_favorite() {
+
+		// Bail if favorites are not active
+		if ( ! bbp_is_favorites_active() ) {
+			bbp_ajax_response( false, esc_html__( 'Favorites are no longer active.', 'bbpress' ), 300 );
+		}
+
+		// Bail if user is not logged in
+		if ( ! is_user_logged_in() ) {
+			bbp_ajax_response( false, esc_html__( 'Please login to favorite.', 'bbpress' ), 301 );
+		}
+
+		// Get user and topic data
+		$user_id = bbp_get_current_user_id();
+		$id      = ! empty( $_POST['id']   ) ? intval( $_POST['id'] )         : 0;
+		$type    = ! empty( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : 'post';
+
+		// Bail if user cannot add favorites for this user
+		if ( ! current_user_can( 'edit_user', $user_id ) ) {
+			bbp_ajax_response( false, esc_html__( 'You do not have permission to do this.', 'bbpress' ), 302 );
+		}
+
+		// Get the object
+		if ( 'post' === $type ) {
+			$object = get_post( $id );
+		}
+
+		// Bail if topic cannot be found
+		if ( empty( $object ) ) {
+			bbp_ajax_response( false, esc_html__( 'Favorite failed.', 'bbpress' ), 303 );
+		}
+
+		// Bail if user did not take this action
+		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'toggle-favorite_' . $object->ID ) ) {
+			bbp_ajax_response( false, esc_html__( 'Are you sure you meant to do that?', 'bbpress' ), 304 );
+		}
+
+		// Take action
+		$status = bbp_is_user_favorite( $user_id, $object->ID )
+			? bbp_remove_user_favorite( $user_id, $object->ID )
+			:    bbp_add_user_favorite( $user_id, $object->ID );
+
+		// Bail if action failed
+		if ( empty( $status ) ) {
+			bbp_ajax_response( false, esc_html__( 'The request was unsuccessful. Please try again.', 'bbpress' ), 305 );
+		}
+
+		// Put subscription attributes in convenient array
+		$attrs = array(
+			'object_id'   => $object->ID,
+			'object_type' => $type,
+			'user_id'     => $user_id
+		);
+
+		// Action succeeded
+		bbp_ajax_response( true, bbp_get_user_favorites_link( $attrs, $user_id, false ), 200 );
+	}
+
+	/**
+	 * AJAX handler to Subscribe/Unsubscribe a user from a topic
+	 *
+	 * @since 2.1.0 bbPress (r3732)
+	 */
+	public function ajax_subscription() {
+
+		// Bail if subscriptions are not active
+		if ( ! bbp_is_subscriptions_active() ) {
+			bbp_ajax_response( false, esc_html__( 'Subscriptions are no longer active.', 'bbpress' ), 300 );
+		}
+
+		// Bail if user is not logged in
+		if ( ! is_user_logged_in() ) {
+			bbp_ajax_response( false, esc_html__( 'Please login to subscribe.', 'bbpress' ), 301 );
+		}
+
+		// Get user and topic data
+		$user_id = bbp_get_current_user_id();
+		$id      = ! empty( $_POST['id']   ) ? intval( $_POST['id'] )         : 0;
+		$type    = ! empty( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : 'post';
+
+		// Bail if user cannot add favorites for this user
+		if ( ! current_user_can( 'edit_user', $user_id ) ) {
+			bbp_ajax_response( false, esc_html__( 'You do not have permission to do this.', 'bbpress' ), 302 );
+		}
+
+		// Get the object
+		if ( 'post' === $type ) {
+			$object = get_post( $id );
+		}
+
+		// Bail if topic cannot be found
+		if ( empty( $object ) ) {
+			bbp_ajax_response( false, esc_html__( 'Subscription failed.', 'bbpress' ), 303 );
+		}
+
+		// Bail if user did not take this action
+		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'toggle-subscription_' . $object->ID ) ) {
+			bbp_ajax_response( false, esc_html__( 'Are you sure you meant to do that?', 'bbpress' ), 304 );
+		}
+
+		// Take action
+		$status = bbp_is_user_subscribed( $user_id, $object->ID )
+			? bbp_remove_user_subscription( $user_id, $object->ID )
+			:    bbp_add_user_subscription( $user_id, $object->ID );
+
+		// Bail if action failed
+		if ( empty( $status ) ) {
+			bbp_ajax_response( false, esc_html__( 'The request was unsuccessful. Please try again.', 'bbpress' ), 305 );
+		}
+
+		// Put subscription attributes in convenient array
+		$attrs = array(
+			'object_id'   => $object->ID,
+			'object_type' => $type,
+			'user_id'     => $user_id
+		);
+
+		// Add separator to topic if favorites is active
+		if ( ( 'post' === $type ) && ( bbp_get_topic_post_type() === get_post_type( $object ) ) && bbp_is_favorites_active() ) {
+			$attrs['before'] = '&nbsp;|&nbsp;';
+		}
+
+		// Action succeeded
+		bbp_ajax_response( true, bbp_get_user_subscribe_link( $attrs, $user_id, false ), 200 );
+	}
+}
+new BBP_Default();
+endif;
Index: /tags/2.6.6/src/templates/default/bbpress/alert-topic-lock.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/alert-topic-lock.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/alert-topic-lock.php	(revision 7162)
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Topic Lock Alert
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_theme_before_alert_topic_lock' ); ?>
+
+<?php if ( bbp_show_topic_lock_alert() ) : ?>
+
+	<div class="bbp-alert-outer">
+		<div class="bbp-alert-inner">
+			<p class="bbp-alert-description"><?php bbp_topic_lock_description(); ?></p>
+			<p class="bbp-alert-actions">
+				<a class="bbp-alert-back" href="<?php bbp_forum_permalink( bbp_get_topic_forum_id() ); ?>"><?php esc_html_e( 'Leave', 'bbpress' ); ?></a>
+				<a class="bbp-alert-close" href="#"><?php esc_html_e( 'Stay', 'bbpress' ); ?></a>
+			</p>
+		</div>
+	</div>
+
+<?php endif;
+
+do_action( 'bbp_theme_after_alert_topic_lock' );
Index: /tags/2.6.6/src/templates/default/bbpress/content-archive-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-archive-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-archive-forum.php	(revision 7162)
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Archive Forum Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_get_template_part( 'form', 'search' ); ?>
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_forum_subscription_link(); ?>
+
+	<?php do_action( 'bbp_template_before_forums_index' ); ?>
+
+	<?php if ( bbp_has_forums() ) : ?>
+
+		<?php bbp_get_template_part( 'loop',     'forums'    ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_get_template_part( 'feedback', 'no-forums' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_forums_index' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-archive-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-archive-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-archive-topic.php	(revision 7162)
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Archive Topic Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php if ( bbp_allow_search() ) : ?>
+
+		<div class="bbp-search-form">
+
+			<?php bbp_get_template_part( 'form', 'search' ); ?>
+
+		</div>
+
+	<?php endif; ?>
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php do_action( 'bbp_template_before_topic_tag_description' ); ?>
+
+	<?php if ( bbp_is_topic_tag() ) : ?>
+
+		<?php bbp_topic_tag_description( array( 'before' => '<div class="bbp-template-notice info"><ul><li>', 'after' => '</li></ul></div>' ) ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_topic_tag_description' ); ?>
+
+	<?php do_action( 'bbp_template_before_topics_index' ); ?>
+
+	<?php if ( bbp_has_topics() ) : ?>
+
+		<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+		<?php bbp_get_template_part( 'loop',       'topics'    ); ?>
+
+		<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_get_template_part( 'feedback',   'no-topics' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_topics_index' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-search.php	(revision 7162)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Search Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_set_query_name( bbp_get_search_rewrite_id() ); ?>
+
+	<?php do_action( 'bbp_template_before_search' ); ?>
+
+	<?php if ( bbp_has_search_results() ) : ?>
+
+		<?php bbp_get_template_part( 'pagination', 'search' ); ?>
+
+		<?php bbp_get_template_part( 'loop',       'search' ); ?>
+
+		<?php bbp_get_template_part( 'pagination', 'search' ); ?>
+
+	<?php elseif ( bbp_get_search_terms() ) : ?>
+
+		<?php bbp_get_template_part( 'feedback',   'no-search' ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_get_template_part( 'form', 'search' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_search_results' ); ?>
+
+</div>
+
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-forum.php	(revision 7162)
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Single Forum Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_forum_subscription_link(); ?>
+
+	<?php do_action( 'bbp_template_before_single_forum' ); ?>
+
+	<?php if ( post_password_required() ) : ?>
+
+		<?php bbp_get_template_part( 'form', 'protected' ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_single_forum_description(); ?>
+
+		<?php if ( bbp_has_forums() ) : ?>
+
+			<?php bbp_get_template_part( 'loop', 'forums' ); ?>
+
+		<?php endif; ?>
+
+		<?php if ( ! bbp_is_forum_category() && bbp_has_topics() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'topics'    ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+			<?php bbp_get_template_part( 'form',       'topic'     ); ?>
+
+		<?php elseif ( ! bbp_is_forum_category() ) : ?>
+
+			<?php bbp_get_template_part( 'feedback',   'no-topics' ); ?>
+
+			<?php bbp_get_template_part( 'form',       'topic'     ); ?>
+
+		<?php endif; ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_single_forum' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-reply.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-reply.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-reply.php	(revision 7162)
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Single Reply Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php do_action( 'bbp_template_before_single_reply' ); ?>
+
+	<?php if ( post_password_required() ) : ?>
+
+		<?php bbp_get_template_part( 'form', 'protected' ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_get_template_part( 'loop', 'single-reply' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_template_after_single_reply' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-topic-lead.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-topic-lead.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-topic-lead.php	(revision 7162)
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Single Topic Lead Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_lead_topic' ); ?>
+
+<ul id="bbp-topic-<?php bbp_topic_id(); ?>-lead" class="bbp-lead-topic">
+
+	<li class="bbp-header">
+
+		<div class="bbp-topic-author"><?php esc_html_e( 'Creator',  'bbpress' ); ?></div><!-- .bbp-topic-author -->
+
+		<div class="bbp-topic-content">
+
+			<?php esc_html_e( 'Topic', 'bbpress' ); ?>
+
+		</div><!-- .bbp-topic-content -->
+
+	</li><!-- .bbp-header -->
+
+	<li class="bbp-body">
+
+		<div class="bbp-topic-header">
+
+			<div class="bbp-meta">
+
+				<span class="bbp-topic-post-date"><?php bbp_topic_post_date(); ?></span>
+
+				<a href="<?php bbp_topic_permalink(); ?>" class="bbp-topic-permalink">#<?php bbp_topic_id(); ?></a>
+
+				<?php do_action( 'bbp_theme_before_topic_admin_links' ); ?>
+
+				<?php bbp_topic_admin_links(); ?>
+
+				<?php do_action( 'bbp_theme_after_topic_admin_links' ); ?>
+
+			</div><!-- .bbp-meta -->
+
+		</div><!-- .bbp-topic-header -->
+
+		<div id="post-<?php bbp_topic_id(); ?>" <?php bbp_topic_class(); ?>>
+
+			<div class="bbp-topic-author">
+
+				<?php do_action( 'bbp_theme_before_topic_author_details' ); ?>
+
+				<?php bbp_topic_author_link( array( 'show_role' => true ) ); ?>
+
+				<?php if ( current_user_can( 'moderate', bbp_get_reply_id() ) ) : ?>
+
+					<?php do_action( 'bbp_theme_before_topic_author_admin_details' ); ?>
+
+					<div class="bbp-topic-ip"><?php bbp_author_ip( bbp_get_topic_id() ); ?></div>
+
+					<?php do_action( 'bbp_theme_after_topic_author_admin_details' ); ?>
+
+				<?php endif; ?>
+
+				<?php do_action( 'bbp_theme_after_topic_author_details' ); ?>
+
+			</div><!-- .bbp-topic-author -->
+
+			<div class="bbp-topic-content">
+
+				<?php do_action( 'bbp_theme_before_topic_content' ); ?>
+
+				<?php bbp_topic_content(); ?>
+
+				<?php do_action( 'bbp_theme_after_topic_content' ); ?>
+
+			</div><!-- .bbp-topic-content -->
+
+		</div><!-- #post-<?php bbp_topic_id(); ?> -->
+
+	</li><!-- .bbp-body -->
+
+	<li class="bbp-footer">
+
+		<div class="bbp-topic-author"><?php esc_html_e( 'Creator',  'bbpress' ); ?></div>
+
+		<div class="bbp-topic-content">
+
+			<?php esc_html_e( 'Topic', 'bbpress' ); ?>
+
+		</div><!-- .bbp-topic-content -->
+
+	</li>
+
+</ul><!-- #bbp-topic-<?php bbp_topic_id(); ?>-lead -->
+
+<?php do_action( 'bbp_template_after_lead_topic' );
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-topic.php	(revision 7162)
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Single Topic Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_topic_subscription_link(); ?>
+
+	<?php bbp_topic_favorite_link(); ?>
+
+	<?php do_action( 'bbp_template_before_single_topic' ); ?>
+
+	<?php if ( post_password_required() ) : ?>
+
+		<?php bbp_get_template_part( 'form', 'protected' ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_topic_tag_list(); ?>
+
+		<?php bbp_single_topic_description(); ?>
+
+		<?php if ( bbp_show_lead_topic() ) : ?>
+
+			<?php bbp_get_template_part( 'content', 'single-topic-lead' ); ?>
+
+		<?php endif; ?>
+
+		<?php if ( bbp_has_replies() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'replies' ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'replies' ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'replies' ); ?>
+
+		<?php endif; ?>
+
+		<?php bbp_get_template_part( 'form', 'reply' ); ?>
+
+	<?php endif; ?>
+
+	<?php bbp_get_template_part( 'alert', 'topic-lock' ); ?>
+
+	<?php do_action( 'bbp_template_after_single_topic' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-user.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-user.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-user.php	(revision 7162)
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Single User Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php do_action( 'bbp_template_before_user_wrapper' ); ?>
+
+	<div id="bbp-user-wrapper">
+
+		<?php bbp_get_template_part( 'user', 'details' ); ?>
+
+		<div id="bbp-user-body">
+			<?php if ( bbp_is_favorites()               ) bbp_get_template_part( 'user', 'favorites'       ); ?>
+			<?php if ( bbp_is_subscriptions()           ) bbp_get_template_part( 'user', 'subscriptions'   ); ?>
+			<?php if ( bbp_is_single_user_engagements() ) bbp_get_template_part( 'user', 'engagements'     ); ?>
+			<?php if ( bbp_is_single_user_topics()      ) bbp_get_template_part( 'user', 'topics-created'  ); ?>
+			<?php if ( bbp_is_single_user_replies()     ) bbp_get_template_part( 'user', 'replies-created' ); ?>
+			<?php if ( bbp_is_single_user_edit()        ) bbp_get_template_part( 'form', 'user-edit'       ); ?>
+			<?php if ( bbp_is_single_user_profile()     ) bbp_get_template_part( 'user', 'profile'         ); ?>
+		</div>
+	</div>
+
+	<?php do_action( 'bbp_template_after_user_wrapper' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-single-view.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-single-view.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-single-view.php	(revision 7162)
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Single View Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_set_query_name( bbp_get_view_rewrite_id() ); ?>
+
+	<?php if ( bbp_view_query() ) : ?>
+
+		<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+		<?php bbp_get_template_part( 'loop',       'topics'    ); ?>
+
+		<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+	<?php else : ?>
+
+		<?php bbp_get_template_part( 'feedback',   'no-topics' ); ?>
+
+	<?php endif; ?>
+
+	<?php bbp_reset_query_name(); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/content-statistics.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-statistics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-statistics.php	(revision 7162)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Statistics Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Get the statistics
+$stats = bbp_get_statistics(); ?>
+
+<dl role="main">
+
+	<?php do_action( 'bbp_before_statistics' ); ?>
+
+	<dt><?php esc_html_e( 'Registered Users', 'bbpress' ); ?></dt>
+	<dd>
+		<strong><?php echo esc_html( $stats['user_count'] ); ?></strong>
+	</dd>
+
+	<dt><?php esc_html_e( 'Forums', 'bbpress' ); ?></dt>
+	<dd>
+		<strong><?php echo esc_html( $stats['forum_count'] ); ?></strong>
+	</dd>
+
+	<dt><?php esc_html_e( 'Topics', 'bbpress' ); ?></dt>
+	<dd>
+		<strong><?php echo esc_html( $stats['topic_count'] ); ?></strong>
+	</dd>
+
+	<dt><?php esc_html_e( 'Replies', 'bbpress' ); ?></dt>
+	<dd>
+		<strong><?php echo esc_html( $stats['reply_count'] ); ?></strong>
+	</dd>
+
+	<dt><?php esc_html_e( 'Topic Tags', 'bbpress' ); ?></dt>
+	<dd>
+		<strong><?php echo esc_html( $stats['topic_tag_count'] ); ?></strong>
+	</dd>
+
+	<?php if ( ! empty( $stats['empty_topic_tag_count'] ) ) : ?>
+
+		<dt><?php esc_html_e( 'Empty Topic Tags', 'bbpress' ); ?></dt>
+		<dd>
+			<strong><?php echo esc_html( $stats['empty_topic_tag_count'] ); ?></strong>
+		</dd>
+
+	<?php endif; ?>
+
+	<?php if ( ! empty( $stats['topic_count_hidden'] ) ) : ?>
+
+		<dt><?php esc_html_e( 'Hidden Topics', 'bbpress' ); ?></dt>
+		<dd>
+			<strong>
+				<abbr title="<?php echo esc_attr( $stats['hidden_topic_title'] ); ?>"><?php echo esc_html( $stats['topic_count_hidden'] ); ?></abbr>
+			</strong>
+		</dd>
+
+	<?php endif; ?>
+
+	<?php if ( ! empty( $stats['reply_count_hidden'] ) ) : ?>
+
+		<dt><?php esc_html_e( 'Hidden Replies', 'bbpress' ); ?></dt>
+		<dd>
+			<strong>
+				<abbr title="<?php echo esc_attr( $stats['hidden_reply_title'] ); ?>"><?php echo esc_html( $stats['reply_count_hidden'] ); ?></abbr>
+			</strong>
+		</dd>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_after_statistics' ); ?>
+
+</dl>
+
+<?php unset( $stats );
Index: /tags/2.6.6/src/templates/default/bbpress/content-topic-tag-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/content-topic-tag-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/content-topic-tag-edit.php	(revision 7162)
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Topic Tag Edit Content Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php do_action( 'bbp_template_before_topic_tag_description' ); ?>
+
+	<?php bbp_topic_tag_description( array( 'before' => '<div class="bbp-template-notice info"><ul><li>', 'after' => '</li></ul></div>' ) ); ?>
+
+	<?php do_action( 'bbp_template_after_topic_tag_description' ); ?>
+
+	<?php do_action( 'bbp_template_before_topic_tag_edit' ); ?>
+
+	<?php bbp_get_template_part( 'form', 'topic-tag' ); ?>
+
+	<?php do_action( 'bbp_template_after_topic_tag_edit' ); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-logged-in.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-logged-in.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-logged-in.php	(revision 7162)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Logged In Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-template-notice info">
+	<ul>
+		<li><?php esc_html_e( 'You are already logged in.', 'bbpress' ); ?></li>
+	</ul>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-no-access.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-no-access.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-no-access.php	(revision 7162)
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * No Access Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="forum-private" class="bbp-forum-content">
+	<h1 class="entry-title"><?php esc_html_e( 'Private', 'bbpress' ); ?></h1>
+	<div class="entry-content">
+		<div class="bbp-template-notice info">
+			<ul>
+				<li><?php esc_html_e( 'You do not have permission to view this forum.', 'bbpress' ); ?></li>
+			</ul>
+		</div>
+	</div>
+</div><!-- #forum-private -->
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-no-forums.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-no-forums.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-no-forums.php	(revision 7162)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * No Forums Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-template-notice">
+	<ul>
+		<li><?php esc_html_e( 'Oh, bother! No forums were found here.', 'bbpress' ); ?></li>
+	</ul>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-no-replies.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-no-replies.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-no-replies.php	(revision 7162)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * No Replies Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-template-notice">
+	<ul>
+		<li><?php esc_html_e( 'Oh, bother! No replies were found here.', 'bbpress' ); ?></li>
+	</ul>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-no-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-no-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-no-search.php	(revision 7162)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * No Search Results Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-template-notice">
+	<ul>
+		<li><?php esc_html_e( 'Oh, bother! No search results were found here.', 'bbpress' ); ?></li>
+	</ul>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/feedback-no-topics.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/feedback-no-topics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/feedback-no-topics.php	(revision 7162)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * No Topics Feedback Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-template-notice">
+	<ul>
+		<li><?php esc_html_e( 'Oh, bother! No topics were found here.', 'bbpress' ); ?></li>
+	</ul>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/form-anonymous.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-anonymous.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-anonymous.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Anonymous User
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_current_user_can_access_anonymous_user_form() ) : ?>
+
+	<?php do_action( 'bbp_theme_before_anonymous_form' ); ?>
+
+	<fieldset class="bbp-form">
+		<legend><?php ( bbp_is_topic_edit() || bbp_is_reply_edit() ) ? esc_html_e( 'Author Information', 'bbpress' ) : esc_html_e( 'Your information:', 'bbpress' ); ?></legend>
+
+		<?php do_action( 'bbp_theme_anonymous_form_extras_top' ); ?>
+
+		<p>
+			<label for="bbp_anonymous_author"><?php esc_html_e( 'Name (required):', 'bbpress' ); ?></label><br />
+			<input type="text" id="bbp_anonymous_author"  value="<?php bbp_author_display_name(); ?>" size="40" maxlength="100" name="bbp_anonymous_name" autocomplete="off" />
+		</p>
+
+		<p>
+			<label for="bbp_anonymous_email"><?php esc_html_e( 'Mail (will not be published) (required):', 'bbpress' ); ?></label><br />
+			<input type="text" id="bbp_anonymous_email"   value="<?php bbp_author_email(); ?>" size="40" maxlength="100" name="bbp_anonymous_email" />
+		</p>
+
+		<p>
+			<label for="bbp_anonymous_website"><?php esc_html_e( 'Website:', 'bbpress' ); ?></label><br />
+			<input type="text" id="bbp_anonymous_website" value="<?php bbp_author_url(); ?>" size="40" maxlength="200" name="bbp_anonymous_website" />
+		</p>
+
+		<?php do_action( 'bbp_theme_anonymous_form_extras_bottom' ); ?>
+
+	</fieldset>
+
+	<?php do_action( 'bbp_theme_after_anonymous_form' ); ?>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-forum.php	(revision 7162)
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * New/Edit Forum
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_is_forum_edit() ) : ?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php bbp_single_forum_description( array( 'forum_id' => bbp_get_forum_id() ) ); ?>
+
+<?php endif; ?>
+
+<?php if ( bbp_current_user_can_access_create_forum_form() ) : ?>
+
+	<div id="new-forum-<?php bbp_forum_id(); ?>" class="bbp-forum-form">
+
+		<form id="new-post" name="new-post" method="post">
+
+			<?php do_action( 'bbp_theme_before_forum_form' ); ?>
+
+			<fieldset class="bbp-form">
+				<legend>
+
+					<?php
+						if ( bbp_is_forum_edit() ) :
+							printf( esc_html__( 'Now Editing &ldquo;%s&rdquo;', 'bbpress' ), bbp_get_forum_title() );
+						else :
+							bbp_is_single_forum()
+								? printf( esc_html__( 'Create New Forum in &ldquo;%s&rdquo;', 'bbpress' ), bbp_get_forum_title() )
+								: esc_html_e( 'Create New Forum', 'bbpress' );
+						endif;
+					?>
+
+				</legend>
+
+				<?php do_action( 'bbp_theme_before_forum_form_notices' ); ?>
+
+				<?php if ( ! bbp_is_forum_edit() && bbp_is_forum_closed() ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'This forum is closed to new content, however your posting capabilities still allow you to post.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php if ( current_user_can( 'unfiltered_html' ) ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'Your account has the ability to post unrestricted HTML content.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php do_action( 'bbp_template_notices' ); ?>
+
+				<div>
+
+					<?php do_action( 'bbp_theme_before_forum_form_title' ); ?>
+
+					<p>
+						<label for="bbp_forum_title"><?php printf( esc_html__( 'Forum Name (Maximum Length: %d):', 'bbpress' ), bbp_get_title_max_length() ); ?></label><br />
+						<input type="text" id="bbp_forum_title" value="<?php bbp_form_forum_title(); ?>" size="40" name="bbp_forum_title" maxlength="<?php bbp_title_max_length(); ?>" />
+					</p>
+
+					<?php do_action( 'bbp_theme_after_forum_form_title' ); ?>
+
+					<?php do_action( 'bbp_theme_before_forum_form_content' ); ?>
+
+					<?php bbp_the_content( array( 'context' => 'forum' ) ); ?>
+
+					<?php do_action( 'bbp_theme_after_forum_form_content' ); ?>
+
+					<?php if ( ! ( bbp_use_wp_editor() || current_user_can( 'unfiltered_html' ) ) ) : ?>
+
+						<p class="form-allowed-tags">
+							<label><?php esc_html_e( 'You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes:','bbpress' ); ?></label><br />
+							<code><?php bbp_allowed_tags(); ?></code>
+						</p>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_allow_forum_mods() && current_user_can( 'assign_moderators' ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_forum_form_mods' ); ?>
+
+						<p>
+							<label for="bbp_moderators"><?php esc_html_e( 'Forum Moderators:', 'bbpress' ); ?></label><br />
+							<input type="text" value="<?php bbp_form_forum_moderators(); ?>" size="40" name="bbp_moderators" id="bbp_moderators" />
+						</p>
+
+						<?php do_action( 'bbp_theme_after_forum_form_mods' ); ?>
+
+					<?php endif; ?>
+
+					<?php do_action( 'bbp_theme_before_forum_form_type' ); ?>
+
+					<p>
+						<label for="bbp_forum_type"><?php esc_html_e( 'Forum Type:', 'bbpress' ); ?></label><br />
+						<?php bbp_form_forum_type_dropdown(); ?>
+					</p>
+
+					<?php do_action( 'bbp_theme_after_forum_form_type' ); ?>
+
+					<?php do_action( 'bbp_theme_before_forum_form_status' ); ?>
+
+					<p>
+						<label for="bbp_forum_status"><?php esc_html_e( 'Status:', 'bbpress' ); ?></label><br />
+						<?php bbp_form_forum_status_dropdown(); ?>
+					</p>
+
+					<?php do_action( 'bbp_theme_after_forum_form_status' ); ?>
+
+					<?php do_action( 'bbp_theme_before_forum_visibility_status' ); ?>
+
+					<p>
+						<label for="bbp_forum_visibility"><?php esc_html_e( 'Visibility:', 'bbpress' ); ?></label><br />
+						<?php bbp_form_forum_visibility_dropdown(); ?>
+					</p>
+
+					<?php do_action( 'bbp_theme_after_forum_visibility_status' ); ?>
+
+					<?php do_action( 'bbp_theme_before_forum_form_parent' ); ?>
+
+					<p>
+						<label for="bbp_forum_parent_id"><?php esc_html_e( 'Parent Forum:', 'bbpress' ); ?></label><br />
+
+						<?php
+							bbp_dropdown( array(
+								'select_id' => 'bbp_forum_parent_id',
+								'show_none' => esc_html__( '&mdash; No parent &mdash;', 'bbpress' ),
+								'selected'  => bbp_get_form_forum_parent(),
+								'exclude'   => bbp_get_forum_id()
+							) );
+						?>
+					</p>
+
+					<?php do_action( 'bbp_theme_after_forum_form_parent' ); ?>
+
+					<?php do_action( 'bbp_theme_before_forum_form_submit_wrapper' ); ?>
+
+					<div class="bbp-submit-wrapper">
+
+						<?php do_action( 'bbp_theme_before_forum_form_submit_button' ); ?>
+
+						<button type="submit" id="bbp_forum_submit" name="bbp_forum_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+
+						<?php do_action( 'bbp_theme_after_forum_form_submit_button' ); ?>
+
+					</div>
+
+					<?php do_action( 'bbp_theme_after_forum_form_submit_wrapper' ); ?>
+
+				</div>
+
+				<?php bbp_forum_form_fields(); ?>
+
+			</fieldset>
+
+			<?php do_action( 'bbp_theme_after_forum_form' ); ?>
+
+		</form>
+	</div>
+
+<?php elseif ( bbp_is_forum_closed() ) : ?>
+
+	<div id="no-forum-<?php bbp_forum_id(); ?>" class="bbp-no-forum">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php printf( esc_html__( 'The forum &#8216;%s&#8217; is closed to new content.', 'bbpress' ), bbp_get_forum_title() ); ?></li>
+			</ul>
+		</div>
+	</div>
+
+<?php else : ?>
+
+	<div id="no-forum-<?php bbp_forum_id(); ?>" class="bbp-no-forum">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php is_user_logged_in()
+					? esc_html_e( 'You cannot create new forums.',               'bbpress' )
+					: esc_html_e( 'You must be logged in to create new forums.', 'bbpress' );
+				?></li>
+			</ul>
+		</div>
+	</div>
+
+<?php endif; ?>
+
+<?php if ( bbp_is_forum_edit() ) : ?>
+
+</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-protected.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-protected.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-protected.php	(revision 7162)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Password Protected
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+	<fieldset class="bbp-form" id="bbp-protected">
+		<Legend><?php esc_html_e( 'Protected', 'bbpress' ); ?></legend>
+
+		<?php echo get_the_password_form(); ?>
+
+	</fieldset>
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/form-reply-move.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-reply-move.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-reply-move.php	(revision 7162)
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * Move Reply
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php if ( is_user_logged_in() && current_user_can( 'edit_topic', bbp_get_topic_id() ) ) : ?>
+
+		<div id="move-reply-<?php bbp_topic_id(); ?>" class="bbp-reply-move">
+
+			<form id="move_reply" name="move_reply" method="post">
+
+				<fieldset class="bbp-form">
+
+					<legend><?php printf( esc_html__( 'Move reply "%s"', 'bbpress' ), bbp_get_reply_title() ); ?></legend>
+
+					<div>
+
+						<div class="bbp-template-notice info">
+							<ul>
+								<li><?php esc_html_e( 'You can either make this reply a new topic with a new title, or merge it into an existing topic.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-template-notice">
+							<ul>
+								<li><?php esc_html_e( 'If you choose an existing topic, replies will be ordered by the time and date they were created.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<fieldset class="bbp-form">
+							<legend><?php esc_html_e( 'Move Method', 'bbpress' ); ?></legend>
+
+							<div>
+								<input name="bbp_reply_move_option" id="bbp_reply_move_option_reply" type="radio" checked="checked" value="topic" />
+								<label for="bbp_reply_move_option_reply"><?php printf( esc_html__( 'New topic in %s titled:', 'bbpress' ), bbp_get_forum_title( bbp_get_reply_forum_id( bbp_get_reply_id() ) ) ); ?></label>
+								<input type="text" id="bbp_reply_move_destination_title" value="<?php printf( esc_html__( 'Moved: %s', 'bbpress' ), bbp_get_reply_title() ); ?>" size="35" name="bbp_reply_move_destination_title" />
+							</div>
+
+							<?php if ( bbp_has_topics( array( 'show_stickies' => false, 'post_parent' => bbp_get_reply_forum_id( bbp_get_reply_id() ), 'post__not_in' => array( bbp_get_reply_topic_id( bbp_get_reply_id() ) ) ) ) ) : ?>
+
+								<div>
+									<input name="bbp_reply_move_option" id="bbp_reply_move_option_existing" type="radio" value="existing" />
+									<label for="bbp_reply_move_option_existing"><?php esc_html_e( 'Use an existing topic in this forum:', 'bbpress' ); ?></label>
+
+									<?php
+										bbp_dropdown( array(
+											'post_type'   => bbp_get_topic_post_type(),
+											'post_parent' => bbp_get_reply_forum_id( bbp_get_reply_id() ),
+											'selected'    => -1,
+											'exclude'     => bbp_get_reply_topic_id( bbp_get_reply_id() ),
+											'select_id'   => 'bbp_destination_topic'
+										) );
+									?>
+
+								</div>
+
+							<?php endif; ?>
+
+						</fieldset>
+
+						<div class="bbp-template-notice error" role="alert" tabindex="-1">
+							<ul>
+								<li><?php esc_html_e( 'This process cannot be undone.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-submit-wrapper">
+							<button type="submit" id="bbp_move_reply_submit" name="bbp_move_reply_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+						</div>
+					</div>
+
+					<?php bbp_move_reply_form_fields(); ?>
+
+				</fieldset>
+			</form>
+		</div>
+
+	<?php else : ?>
+
+		<div id="no-reply-<?php bbp_reply_id(); ?>" class="bbp-no-reply">
+			<div class="entry-content"><?php is_user_logged_in()
+				? esc_html_e( 'You do not have permission to edit this reply.', 'bbpress' )
+				: esc_html_e( 'You cannot edit this reply.',                    'bbpress' );
+			?></div>
+		</div>
+
+	<?php endif; ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/form-reply-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-reply-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-reply-search.php	(revision 7162)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Search
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_allow_search() ) : ?>
+
+	<div class="bbp-search-form">
+		<form role="search" method="get" id="bbp-reply-search-form">
+			<div>
+				<label class="screen-reader-text hidden" for="rs"><?php esc_html_e( 'Search replies:', 'bbpress' ); ?></label>
+				<input type="text" value="<?php bbp_search_terms(); ?>" name="rs" id="rs" />
+				<input class="button" type="submit" id="bbp_search_submit" value="<?php esc_attr_e( 'Search', 'bbpress' ); ?>" />
+			</div>
+		</form>
+	</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-reply.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-reply.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-reply.php	(revision 7162)
@@ -0,0 +1,241 @@
+<?php
+
+/**
+ * New/Edit Reply
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_is_reply_edit() ) : ?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+<?php endif; ?>
+
+<?php if ( bbp_current_user_can_access_create_reply_form() ) : ?>
+
+	<div id="new-reply-<?php bbp_topic_id(); ?>" class="bbp-reply-form">
+
+		<form id="new-post" name="new-post" method="post">
+
+			<?php do_action( 'bbp_theme_before_reply_form' ); ?>
+
+			<fieldset class="bbp-form">
+				<legend><?php printf( esc_html__( 'Reply To: %s', 'bbpress' ), ( bbp_get_form_reply_to() ) ? sprintf( esc_html__( 'Reply #%1$s in %2$s', 'bbpress' ), bbp_get_form_reply_to(), bbp_get_topic_title() ) : bbp_get_topic_title() ); ?></legend>
+
+				<?php do_action( 'bbp_theme_before_reply_form_notices' ); ?>
+
+				<?php if ( ! bbp_is_topic_open() && ! bbp_is_reply_edit() ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'This topic is marked as closed to new replies, however your posting capabilities still allow you to reply.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php if ( ! bbp_is_reply_edit() && bbp_is_forum_closed() ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'This forum is closed to new content, however your posting capabilities still allow you to post.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php if ( current_user_can( 'unfiltered_html' ) ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'Your account has the ability to post unrestricted HTML content.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php do_action( 'bbp_template_notices' ); ?>
+
+				<div>
+
+					<?php bbp_get_template_part( 'form', 'anonymous' ); ?>
+
+					<?php do_action( 'bbp_theme_before_reply_form_content' ); ?>
+
+					<?php bbp_the_content( array( 'context' => 'reply' ) ); ?>
+
+					<?php do_action( 'bbp_theme_after_reply_form_content' ); ?>
+
+					<?php if ( ! ( bbp_use_wp_editor() || current_user_can( 'unfiltered_html' ) ) ) : ?>
+
+						<p class="form-allowed-tags">
+							<label><?php esc_html_e( 'You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes:','bbpress' ); ?></label><br />
+							<code><?php bbp_allowed_tags(); ?></code>
+						</p>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', bbp_get_topic_id() ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_reply_form_tags' ); ?>
+
+						<p>
+							<label for="bbp_topic_tags"><?php esc_html_e( 'Tags:', 'bbpress' ); ?></label><br />
+							<input type="text" value="<?php bbp_form_topic_tags(); ?>" size="40" name="bbp_topic_tags" id="bbp_topic_tags" <?php disabled( bbp_is_topic_spam() ); ?> />
+						</p>
+
+						<?php do_action( 'bbp_theme_after_reply_form_tags' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_is_subscriptions_active() && ! bbp_is_anonymous() && ( ! bbp_is_reply_edit() || ( bbp_is_reply_edit() && ! bbp_is_reply_anonymous() ) ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_reply_form_subscription' ); ?>
+
+						<p>
+
+							<input name="bbp_topic_subscription" id="bbp_topic_subscription" type="checkbox" value="bbp_subscribe"<?php bbp_form_topic_subscribed(); ?> />
+
+							<?php if ( bbp_is_reply_edit() && ( bbp_get_reply_author_id() !== bbp_get_current_user_id() ) ) : ?>
+
+								<label for="bbp_topic_subscription"><?php esc_html_e( 'Notify the author of follow-up replies via email', 'bbpress' ); ?></label>
+
+							<?php else : ?>
+
+								<label for="bbp_topic_subscription"><?php esc_html_e( 'Notify me of follow-up replies via email', 'bbpress' ); ?></label>
+
+							<?php endif; ?>
+
+						</p>
+
+						<?php do_action( 'bbp_theme_after_reply_form_subscription' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_is_reply_edit() ) : ?>
+
+						<?php if ( current_user_can( 'moderate', bbp_get_reply_id() ) ) : ?>
+
+							<?php do_action( 'bbp_theme_before_reply_form_reply_to' ); ?>
+
+							<p class="form-reply-to">
+								<label for="bbp_reply_to"><?php esc_html_e( 'Reply To:', 'bbpress' ); ?></label><br />
+								<?php bbp_reply_to_dropdown(); ?>
+							</p>
+
+							<?php do_action( 'bbp_theme_after_reply_form_reply_to' ); ?>
+
+							<?php do_action( 'bbp_theme_before_reply_form_status' ); ?>
+
+							<p>
+								<label for="bbp_reply_status"><?php esc_html_e( 'Reply Status:', 'bbpress' ); ?></label><br />
+								<?php bbp_form_reply_status_dropdown(); ?>
+							</p>
+
+							<?php do_action( 'bbp_theme_after_reply_form_status' ); ?>
+
+						<?php endif; ?>
+
+						<?php if ( bbp_allow_revisions() ) : ?>
+
+							<?php do_action( 'bbp_theme_before_reply_form_revisions' ); ?>
+
+							<fieldset class="bbp-form">
+								<legend>
+									<input name="bbp_log_reply_edit" id="bbp_log_reply_edit" type="checkbox" value="1" <?php bbp_form_reply_log_edit(); ?> />
+									<label for="bbp_log_reply_edit"><?php esc_html_e( 'Keep a log of this edit:', 'bbpress' ); ?></label><br />
+								</legend>
+
+								<div>
+									<label for="bbp_reply_edit_reason"><?php printf( esc_html__( 'Optional reason for editing:', 'bbpress' ), bbp_get_current_user_name() ); ?></label><br />
+									<input type="text" value="<?php bbp_form_reply_edit_reason(); ?>" size="40" name="bbp_reply_edit_reason" id="bbp_reply_edit_reason" />
+								</div>
+							</fieldset>
+
+							<?php do_action( 'bbp_theme_after_reply_form_revisions' ); ?>
+
+						<?php endif; ?>
+
+					<?php endif; ?>
+
+					<?php do_action( 'bbp_theme_before_reply_form_submit_wrapper' ); ?>
+
+					<div class="bbp-submit-wrapper">
+
+						<?php do_action( 'bbp_theme_before_reply_form_submit_button' ); ?>
+
+						<?php bbp_cancel_reply_to_link(); ?>
+
+						<button type="submit" id="bbp_reply_submit" name="bbp_reply_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+
+						<?php do_action( 'bbp_theme_after_reply_form_submit_button' ); ?>
+
+					</div>
+
+					<?php do_action( 'bbp_theme_after_reply_form_submit_wrapper' ); ?>
+
+				</div>
+
+				<?php bbp_reply_form_fields(); ?>
+
+			</fieldset>
+
+			<?php do_action( 'bbp_theme_after_reply_form' ); ?>
+
+		</form>
+	</div>
+
+<?php elseif ( bbp_is_topic_closed() ) : ?>
+
+	<div id="no-reply-<?php bbp_topic_id(); ?>" class="bbp-no-reply">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php printf( esc_html__( 'The topic &#8216;%s&#8217; is closed to new replies.', 'bbpress' ), bbp_get_topic_title() ); ?></li>
+			</ul>
+		</div>
+	</div>
+
+<?php elseif ( bbp_is_forum_closed( bbp_get_topic_forum_id() ) ) : ?>
+
+	<div id="no-reply-<?php bbp_topic_id(); ?>" class="bbp-no-reply">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php printf( esc_html__( 'The forum &#8216;%s&#8217; is closed to new topics and replies.', 'bbpress' ), bbp_get_forum_title( bbp_get_topic_forum_id() ) ); ?></li>
+			</ul>
+		</div>
+	</div>
+
+<?php else : ?>
+
+	<div id="no-reply-<?php bbp_topic_id(); ?>" class="bbp-no-reply">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php is_user_logged_in()
+					? esc_html_e( 'You cannot reply to this topic.',               'bbpress' )
+					: esc_html_e( 'You must be logged in to reply to this topic.', 'bbpress' );
+				?></li>
+			</ul>
+		</div>
+
+		<?php if ( ! is_user_logged_in() ) : ?>
+
+			<?php bbp_get_template_part( 'form', 'user-login' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+
+<?php endif; ?>
+
+<?php if ( bbp_is_reply_edit() ) : ?>
+
+</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-search.php	(revision 7162)
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Search 
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_allow_search() ) : ?>
+
+	<div class="bbp-search-form">
+		<form role="search" method="get" id="bbp-search-form">
+			<div>
+				<label class="screen-reader-text hidden" for="bbp_search"><?php esc_html_e( 'Search for:', 'bbpress' ); ?></label>
+				<input type="hidden" name="action" value="bbp-search-request" />
+				<input type="text" value="<?php bbp_search_terms(); ?>" name="bbp_search" id="bbp_search" />
+				<input class="button" type="submit" id="bbp_search_submit" value="<?php esc_attr_e( 'Search', 'bbpress' ); ?>" />
+			</div>
+		</form>
+	</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-topic-merge.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-topic-merge.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-topic-merge.php	(revision 7162)
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * Merge Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php if ( is_user_logged_in() && current_user_can( 'edit_topic', bbp_get_topic_id() ) ) : ?>
+
+		<div id="merge-topic-<?php bbp_topic_id(); ?>" class="bbp-topic-merge">
+
+			<form id="merge_topic" name="merge_topic" method="post">
+
+				<fieldset class="bbp-form">
+
+					<legend><?php printf( esc_html__( 'Merge topic "%s"', 'bbpress' ), bbp_get_topic_title() ); ?></legend>
+
+					<div>
+
+						<div class="bbp-template-notice info">
+							<ul>
+								<li><?php esc_html_e( 'Select the topic to merge this one into. The destination topic will remain the lead topic, and this one will change into a reply.', 'bbpress' ); ?></li>
+								<li><?php esc_html_e( 'To keep this topic as the lead, go to the other topic and use the merge tool from there instead.',                                  'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-template-notice">
+							<ul>
+								<li><?php esc_html_e( 'Replies to both topics are merged chronologically, ordered by the time and date they were published. Topics may be updated to a 1 second difference to maintain chronological order based on the merge direction.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<fieldset class="bbp-form">
+							<legend><?php esc_html_e( 'Destination', 'bbpress' ); ?></legend>
+							<div>
+								<?php if ( bbp_has_topics( array( 'show_stickies' => false, 'post_parent' => bbp_get_topic_forum_id( bbp_get_topic_id() ), 'post__not_in' => array( bbp_get_topic_id() ) ) ) ) : ?>
+
+									<label for="bbp_destination_topic"><?php esc_html_e( 'Merge with this topic:', 'bbpress' ); ?></label>
+
+									<?php
+										bbp_dropdown( array(
+											'post_type'   => bbp_get_topic_post_type(),
+											'post_parent' => bbp_get_topic_forum_id( bbp_get_topic_id() ),
+											'post_status' => bbp_get_public_topic_statuses(),
+											'selected'    => -1,
+											'exclude'     => bbp_get_topic_id(),
+											'select_id'   => 'bbp_destination_topic'
+										) );
+									?>
+
+								<?php else : ?>
+
+									<label><?php esc_html_e( 'There are no other topics in this forum to merge with.', 'bbpress' ); ?></label>
+
+								<?php endif; ?>
+
+							</div>
+						</fieldset>
+
+						<fieldset class="bbp-form">
+							<legend><?php esc_html_e( 'Topic Extras', 'bbpress' ); ?></legend>
+
+							<div>
+
+								<?php if ( bbp_is_subscriptions_active() ) : ?>
+
+									<input name="bbp_topic_subscribers" id="bbp_topic_subscribers" type="checkbox" value="1" checked="checked" />
+									<label for="bbp_topic_subscribers"><?php esc_html_e( 'Merge topic subscribers', 'bbpress' ); ?></label><br />
+
+								<?php endif; ?>
+
+								<input name="bbp_topic_favoriters" id="bbp_topic_favoriters" type="checkbox" value="1" checked="checked" />
+								<label for="bbp_topic_favoriters"><?php esc_html_e( 'Merge topic favoriters', 'bbpress' ); ?></label><br />
+
+								<?php if ( bbp_allow_topic_tags() ) : ?>
+
+									<input name="bbp_topic_tags" id="bbp_topic_tags" type="checkbox" value="1" checked="checked" />
+									<label for="bbp_topic_tags"><?php esc_html_e( 'Merge topic tags', 'bbpress' ); ?></label><br />
+
+								<?php endif; ?>
+
+							</div>
+						</fieldset>
+
+						<div class="bbp-template-notice error">
+							<ul>
+								<li><?php esc_html_e( 'This process cannot be undone.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-submit-wrapper">
+							<button type="submit" id="bbp_merge_topic_submit" name="bbp_merge_topic_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+						</div>
+					</div>
+
+					<?php bbp_merge_topic_form_fields(); ?>
+
+				</fieldset>
+			</form>
+		</div>
+
+	<?php else : ?>
+
+		<div id="no-topic-<?php bbp_topic_id(); ?>" class="bbp-no-topic">
+			<div class="entry-content"><?php is_user_logged_in()
+				? esc_html_e( 'You do not have permission to edit this topic.', 'bbpress' )
+				: esc_html_e( 'You cannot edit this topic.',                    'bbpress' );
+			?></div>
+		</div>
+
+	<?php endif; ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/form-topic-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-topic-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-topic-search.php	(revision 7162)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Search
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( bbp_allow_search() ) : ?>
+
+	<div class="bbp-search-form">
+		<form role="search" method="get" id="bbp-topic-search-form">
+			<div>
+				<label class="screen-reader-text hidden" for="ts"><?php esc_html_e( 'Search topics:', 'bbpress' ); ?></label>
+				<input type="text" value="<?php bbp_search_terms(); ?>" name="ts" id="ts" />
+				<input class="button" type="submit" id="bbp_search_submit" value="<?php esc_attr_e( 'Search', 'bbpress' ); ?>" />
+			</div>
+		</form>
+	</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-topic-split.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-topic-split.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-topic-split.php	(revision 7162)
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Split Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+	<?php if ( is_user_logged_in() && current_user_can( 'edit_topic', bbp_get_topic_id() ) ) : ?>
+
+		<div id="split-topic-<?php bbp_topic_id(); ?>" class="bbp-topic-split">
+
+			<form id="split_topic" name="split_topic" method="post">
+
+				<fieldset class="bbp-form">
+
+					<legend><?php printf( esc_html__( 'Split topic "%s"', 'bbpress' ), bbp_get_topic_title() ); ?></legend>
+
+					<div>
+
+						<div class="bbp-template-notice info">
+							<ul>
+								<li><?php esc_html_e( 'When you split a topic, you are slicing it in half starting with the reply you just selected. Choose to use that reply as a new topic with a new title, or merge those replies into an existing topic.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-template-notice">
+							<ul>
+								<li><?php esc_html_e( 'If you use the existing topic option, replies within both topics will be merged chronologically. The order of the merged replies is based on the time and date they were posted.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<fieldset class="bbp-form">
+							<legend><?php esc_html_e( 'Split Method', 'bbpress' ); ?></legend>
+
+							<div>
+								<input name="bbp_topic_split_option" id="bbp_topic_split_option_reply" type="radio" checked="checked" value="reply" />
+								<label for="bbp_topic_split_option_reply"><?php printf( esc_html__( 'New topic in %s titled:', 'bbpress' ), bbp_get_forum_title( bbp_get_topic_forum_id( bbp_get_topic_id() ) ) ); ?></label>
+								<input type="text" id="bbp_topic_split_destination_title" value="<?php printf( esc_html__( 'Split: %s', 'bbpress' ), bbp_get_topic_title() ); ?>" size="35" name="bbp_topic_split_destination_title" />
+							</div>
+
+							<?php if ( bbp_has_topics( array( 'show_stickies' => false, 'post_parent' => bbp_get_topic_forum_id( bbp_get_topic_id() ), 'post__not_in' => array( bbp_get_topic_id() ) ) ) ) : ?>
+
+								<div>
+									<input name="bbp_topic_split_option" id="bbp_topic_split_option_existing" type="radio" value="existing" />
+									<label for="bbp_topic_split_option_existing"><?php esc_html_e( 'Use an existing topic in this forum:', 'bbpress' ); ?></label>
+
+									<?php
+										bbp_dropdown( array(
+											'post_type'   => bbp_get_topic_post_type(),
+											'post_parent' => bbp_get_topic_forum_id( bbp_get_topic_id() ),
+											'post_status' => bbp_get_public_topic_statuses(),
+											'selected'    => -1,
+											'exclude'     => bbp_get_topic_id(),
+											'select_id'   => 'bbp_destination_topic'
+										) );
+									?>
+
+								</div>
+
+							<?php endif; ?>
+
+						</fieldset>
+
+						<fieldset class="bbp-form">
+							<legend><?php esc_html_e( 'Topic Extras', 'bbpress' ); ?></legend>
+
+							<div>
+
+								<?php if ( bbp_is_subscriptions_active() ) : ?>
+
+									<input name="bbp_topic_subscribers" id="bbp_topic_subscribers" type="checkbox" value="1" checked="checked" />
+									<label for="bbp_topic_subscribers"><?php esc_html_e( 'Copy subscribers to the new topic', 'bbpress' ); ?></label><br />
+
+								<?php endif; ?>
+
+								<input name="bbp_topic_favoriters" id="bbp_topic_favoriters" type="checkbox" value="1" checked="checked" />
+								<label for="bbp_topic_favoriters"><?php esc_html_e( 'Copy favoriters to the new topic', 'bbpress' ); ?></label><br />
+
+								<?php if ( bbp_allow_topic_tags() ) : ?>
+
+									<input name="bbp_topic_tags" id="bbp_topic_tags" type="checkbox" value="1" checked="checked" />
+									<label for="bbp_topic_tags"><?php esc_html_e( 'Copy topic tags to the new topic', 'bbpress' ); ?></label><br />
+
+								<?php endif; ?>
+
+							</div>
+						</fieldset>
+
+						<div class="bbp-template-notice error" role="alert" tabindex="-1">
+							<ul>
+								<li><?php esc_html_e( 'This process cannot be undone.', 'bbpress' ); ?></li>
+							</ul>
+						</div>
+
+						<div class="bbp-submit-wrapper">
+							<button type="submit" id="bbp_merge_topic_submit" name="bbp_merge_topic_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+						</div>
+					</div>
+
+					<?php bbp_split_topic_form_fields(); ?>
+
+				</fieldset>
+			</form>
+		</div>
+
+	<?php else : ?>
+
+		<div id="no-topic-<?php bbp_topic_id(); ?>" class="bbp-no-topic">
+			<div class="entry-content"><?php is_user_logged_in()
+				? esc_html_e( 'You do not have permission to edit this topic.', 'bbpress' )
+				: esc_html_e( 'You cannot edit this topic.',                    'bbpress' );
+			?></div>
+		</div>
+
+	<?php endif; ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/form-topic-tag.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-topic-tag.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-topic-tag.php	(revision 7162)
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * Edit Topic Tag
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( current_user_can( 'edit_topic_tags' ) ) : ?>
+
+	<div id="edit-topic-tag-<?php bbp_topic_tag_id(); ?>" class="bbp-topic-tag-form">
+
+		<fieldset class="bbp-form" id="bbp-edit-topic-tag">
+
+			<legend><?php printf( esc_html__( 'Manage Tag: "%s"', 'bbpress' ), bbp_get_topic_tag_name() ); ?></legend>
+
+			<fieldset class="bbp-form" id="tag-rename">
+
+				<legend><?php esc_html_e( 'Rename', 'bbpress' ); ?></legend>
+
+				<div class="bbp-template-notice info">
+					<ul>
+						<li><?php esc_html_e( 'Leave the slug empty to have one automatically generated.', 'bbpress' ); ?></li>
+					</ul>
+				</div>
+
+				<div class="bbp-template-notice">
+					<ul>
+						<li><?php esc_html_e( 'Changing the slug affects its permalink. Any links to the old slug will stop working.', 'bbpress' ); ?></li>
+					</ul>
+				</div>
+
+				<form id="rename_tag" name="rename_tag" method="post">
+
+					<div>
+						<label for="tag-name"><?php esc_html_e( 'Name:', 'bbpress' ); ?></label>
+						<input type="text" id="tag-name" name="tag-name" size="20" maxlength="40" value="<?php echo esc_attr( bbp_get_topic_tag_name() ); ?>" />
+					</div>
+
+					<div>
+						<label for="tag-slug"><?php esc_html_e( 'Slug:', 'bbpress' ); ?></label>
+						<input type="text" id="tag-slug" name="tag-slug" size="20" maxlength="40" value="<?php echo esc_attr( apply_filters( 'editable_slug', bbp_get_topic_tag_slug() ) ); ?>" />
+					</div>
+
+					<div>
+						<label for="tag-description"><?php esc_html_e( 'Description:', 'bbpress' ); ?></label>
+						<input type="text" id="tag-description" name="tag-description" size="20" value="<?php echo esc_attr( bbp_get_topic_tag_description( array( 'before' => '', 'after' => '' ) ) ); ?>" />
+					</div>
+
+					<div class="bbp-submit-wrapper">
+						<button type="submit" class="button submit"><?php esc_attr_e( 'Update', 'bbpress' ); ?></button>
+
+						<input type="hidden" name="tag-id" value="<?php bbp_topic_tag_id(); ?>" />
+						<input type="hidden" name="action" value="bbp-update-topic-tag" />
+
+						<?php wp_nonce_field( 'update-tag_' . bbp_get_topic_tag_id() ); ?>
+
+					</div>
+				</form>
+
+			</fieldset>
+
+			<fieldset class="bbp-form" id="tag-merge">
+
+				<legend><?php esc_html_e( 'Merge', 'bbpress' ); ?></legend>
+
+				<div class="bbp-template-notice">
+					<ul>
+						<li><?php esc_html_e( 'Merging tags together cannot be undone.', 'bbpress' ); ?></li>
+					</ul>
+				</div>
+
+				<form id="merge_tag" name="merge_tag" method="post">
+
+					<div>
+						<label for="tag-existing-name"><?php esc_html_e( 'Existing tag:', 'bbpress' ); ?></label>
+						<input type="text" id="tag-existing-name" name="tag-existing-name" size="22" maxlength="40" />
+					</div>
+
+					<div class="bbp-submit-wrapper">
+						<button type="submit" class="button submit" onclick="return confirm('<?php echo esc_js( sprintf( esc_html__( 'Are you sure you want to merge the "%s" tag into the tag you specified?', 'bbpress' ), bbp_get_topic_tag_name() ) ); ?>');"><?php esc_attr_e( 'Merge', 'bbpress' ); ?></button>
+
+						<input type="hidden" name="tag-id" value="<?php bbp_topic_tag_id(); ?>" />
+						<input type="hidden" name="action" value="bbp-merge-topic-tag" />
+
+						<?php wp_nonce_field( 'merge-tag_' . bbp_get_topic_tag_id() ); ?>
+					</div>
+				</form>
+
+			</fieldset>
+
+			<?php if ( current_user_can( 'delete_topic_tags' ) ) : ?>
+
+				<fieldset class="bbp-form" id="delete-tag">
+
+					<legend><?php esc_html_e( 'Delete', 'bbpress' ); ?></legend>
+
+					<div class="bbp-template-notice info">
+						<ul>
+							<li><?php esc_html_e( 'This does not delete your topics. Only the tag itself is deleted.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'Deleting a tag cannot be undone.', 'bbpress' ); ?></li>
+							<li><?php esc_html_e( 'Any links to this tag will no longer function.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+					<form id="delete_tag" name="delete_tag" method="post">
+
+						<div class="bbp-submit-wrapper">
+							<button type="submit" class="button submit" onclick="return confirm('<?php echo esc_js( sprintf( esc_html__( 'Are you sure you want to delete the "%s" tag? This is permanent and cannot be undone.', 'bbpress' ), bbp_get_topic_tag_name() ) ); ?>');"><?php esc_attr_e( 'Delete', 'bbpress' ); ?></button>
+
+							<input type="hidden" name="tag-id" value="<?php bbp_topic_tag_id(); ?>" />
+							<input type="hidden" name="action" value="bbp-delete-topic-tag" />
+
+							<?php wp_nonce_field( 'delete-tag_' . bbp_get_topic_tag_id() ); ?>
+						</div>
+					</form>
+
+				</fieldset>
+
+			<?php endif; ?>
+
+		</fieldset>
+	</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-topic.php	(revision 7162)
@@ -0,0 +1,268 @@
+<?php
+
+/**
+ * New/Edit Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+if ( ! bbp_is_single_forum() ) : ?>
+
+<div id="bbpress-forums" class="bbpress-wrapper">
+
+	<?php bbp_breadcrumb(); ?>
+
+<?php endif; ?>
+
+<?php if ( bbp_is_topic_edit() ) : ?>
+
+	<?php bbp_topic_tag_list( bbp_get_topic_id() ); ?>
+
+	<?php bbp_single_topic_description( array( 'topic_id' => bbp_get_topic_id() ) ); ?>
+
+	<?php bbp_get_template_part( 'alert', 'topic-lock' ); ?>
+
+<?php endif; ?>
+
+<?php if ( bbp_current_user_can_access_create_topic_form() ) : ?>
+
+	<div id="new-topic-<?php bbp_topic_id(); ?>" class="bbp-topic-form">
+
+		<form id="new-post" name="new-post" method="post">
+
+			<?php do_action( 'bbp_theme_before_topic_form' ); ?>
+
+			<fieldset class="bbp-form">
+				<legend>
+
+					<?php
+						if ( bbp_is_topic_edit() ) :
+							printf( esc_html__( 'Now Editing &ldquo;%s&rdquo;', 'bbpress' ), bbp_get_topic_title() );
+						else :
+							( bbp_is_single_forum() && bbp_get_forum_title() )
+								? printf( esc_html__( 'Create New Topic in &ldquo;%s&rdquo;', 'bbpress' ), bbp_get_forum_title() )
+								: esc_html_e( 'Create New Topic', 'bbpress' );
+						endif;
+					?>
+
+				</legend>
+
+				<?php do_action( 'bbp_theme_before_topic_form_notices' ); ?>
+
+				<?php if ( ! bbp_is_topic_edit() && bbp_is_forum_closed() ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'This forum is marked as closed to new topics, however your posting capabilities still allow you to create a topic.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php if ( current_user_can( 'unfiltered_html' ) ) : ?>
+
+					<div class="bbp-template-notice">
+						<ul>
+							<li><?php esc_html_e( 'Your account has the ability to post unrestricted HTML content.', 'bbpress' ); ?></li>
+						</ul>
+					</div>
+
+				<?php endif; ?>
+
+				<?php do_action( 'bbp_template_notices' ); ?>
+
+				<div>
+
+					<?php bbp_get_template_part( 'form', 'anonymous' ); ?>
+
+					<?php do_action( 'bbp_theme_before_topic_form_title' ); ?>
+
+					<p>
+						<label for="bbp_topic_title"><?php printf( esc_html__( 'Topic Title (Maximum Length: %d):', 'bbpress' ), bbp_get_title_max_length() ); ?></label><br />
+						<input type="text" id="bbp_topic_title" value="<?php bbp_form_topic_title(); ?>" size="40" name="bbp_topic_title" maxlength="<?php bbp_title_max_length(); ?>" />
+					</p>
+
+					<?php do_action( 'bbp_theme_after_topic_form_title' ); ?>
+
+					<?php do_action( 'bbp_theme_before_topic_form_content' ); ?>
+
+					<?php bbp_the_content( array( 'context' => 'topic' ) ); ?>
+
+					<?php do_action( 'bbp_theme_after_topic_form_content' ); ?>
+
+					<?php if ( ! ( bbp_use_wp_editor() || current_user_can( 'unfiltered_html' ) ) ) : ?>
+
+						<p class="form-allowed-tags">
+							<label><?php printf( esc_html__( 'You may use these %s tags and attributes:', 'bbpress' ), '<abbr title="HyperText Markup Language">HTML</abbr>' ); ?></label><br />
+							<code><?php bbp_allowed_tags(); ?></code>
+						</p>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', bbp_get_topic_id() ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_tags' ); ?>
+
+						<p>
+							<label for="bbp_topic_tags"><?php esc_html_e( 'Topic Tags:', 'bbpress' ); ?></label><br />
+							<input type="text" value="<?php bbp_form_topic_tags(); ?>" size="40" name="bbp_topic_tags" id="bbp_topic_tags" <?php disabled( bbp_is_topic_spam() ); ?> />
+						</p>
+
+						<?php do_action( 'bbp_theme_after_topic_form_tags' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( ! bbp_is_single_forum() ) : ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_forum' ); ?>
+
+						<p>
+							<label for="bbp_forum_id"><?php esc_html_e( 'Forum:', 'bbpress' ); ?></label><br />
+							<?php
+								bbp_dropdown( array(
+									'show_none' => esc_html__( '&mdash; No forum &mdash;', 'bbpress' ),
+									'selected'  => bbp_get_form_topic_forum()
+								) );
+							?>
+						</p>
+
+						<?php do_action( 'bbp_theme_after_topic_form_forum' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( current_user_can( 'moderate', bbp_get_topic_id() ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_type' ); ?>
+
+						<p>
+
+							<label for="bbp_stick_topic"><?php esc_html_e( 'Topic Type:', 'bbpress' ); ?></label><br />
+
+							<?php bbp_form_topic_type_dropdown(); ?>
+
+						</p>
+
+						<?php do_action( 'bbp_theme_after_topic_form_type' ); ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_status' ); ?>
+
+						<p>
+
+							<label for="bbp_topic_status"><?php esc_html_e( 'Topic Status:', 'bbpress' ); ?></label><br />
+
+							<?php bbp_form_topic_status_dropdown(); ?>
+
+						</p>
+
+						<?php do_action( 'bbp_theme_after_topic_form_status' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_is_subscriptions_active() && ! bbp_is_anonymous() && ( ! bbp_is_topic_edit() || ( bbp_is_topic_edit() && ! bbp_is_topic_anonymous() ) ) ) : ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_subscriptions' ); ?>
+
+						<p>
+							<input name="bbp_topic_subscription" id="bbp_topic_subscription" type="checkbox" value="bbp_subscribe" <?php bbp_form_topic_subscribed(); ?> />
+
+							<?php if ( bbp_is_topic_edit() && ( bbp_get_topic_author_id() !== bbp_get_current_user_id() ) ) : ?>
+
+								<label for="bbp_topic_subscription"><?php esc_html_e( 'Notify the author of follow-up replies via email', 'bbpress' ); ?></label>
+
+							<?php else : ?>
+
+								<label for="bbp_topic_subscription"><?php esc_html_e( 'Notify me of follow-up replies via email', 'bbpress' ); ?></label>
+
+							<?php endif; ?>
+						</p>
+
+						<?php do_action( 'bbp_theme_after_topic_form_subscriptions' ); ?>
+
+					<?php endif; ?>
+
+					<?php if ( bbp_allow_revisions() && bbp_is_topic_edit() ) : ?>
+
+						<?php do_action( 'bbp_theme_before_topic_form_revisions' ); ?>
+
+						<fieldset class="bbp-form">
+							<legend>
+								<input name="bbp_log_topic_edit" id="bbp_log_topic_edit" type="checkbox" value="1" <?php bbp_form_topic_log_edit(); ?> />
+								<label for="bbp_log_topic_edit"><?php esc_html_e( 'Keep a log of this edit:', 'bbpress' ); ?></label><br />
+							</legend>
+
+							<div>
+								<label for="bbp_topic_edit_reason"><?php printf( esc_html__( 'Optional reason for editing:', 'bbpress' ), bbp_get_current_user_name() ); ?></label><br />
+								<input type="text" value="<?php bbp_form_topic_edit_reason(); ?>" size="40" name="bbp_topic_edit_reason" id="bbp_topic_edit_reason" />
+							</div>
+						</fieldset>
+
+						<?php do_action( 'bbp_theme_after_topic_form_revisions' ); ?>
+
+					<?php endif; ?>
+
+					<?php do_action( 'bbp_theme_before_topic_form_submit_wrapper' ); ?>
+
+					<div class="bbp-submit-wrapper">
+
+						<?php do_action( 'bbp_theme_before_topic_form_submit_button' ); ?>
+
+						<button type="submit" id="bbp_topic_submit" name="bbp_topic_submit" class="button submit"><?php esc_html_e( 'Submit', 'bbpress' ); ?></button>
+
+						<?php do_action( 'bbp_theme_after_topic_form_submit_button' ); ?>
+
+					</div>
+
+					<?php do_action( 'bbp_theme_after_topic_form_submit_wrapper' ); ?>
+
+				</div>
+
+				<?php bbp_topic_form_fields(); ?>
+
+			</fieldset>
+
+			<?php do_action( 'bbp_theme_after_topic_form' ); ?>
+
+		</form>
+	</div>
+
+<?php elseif ( bbp_is_forum_closed() ) : ?>
+
+	<div id="forum-closed-<?php bbp_forum_id(); ?>" class="bbp-forum-closed">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php printf( esc_html__( 'The forum &#8216;%s&#8217; is closed to new topics and replies.', 'bbpress' ), bbp_get_forum_title() ); ?></li>
+			</ul>
+		</div>
+	</div>
+
+<?php else : ?>
+
+	<div id="no-topic-<?php bbp_forum_id(); ?>" class="bbp-no-topic">
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php is_user_logged_in()
+					? esc_html_e( 'You cannot create new topics.',               'bbpress' )
+					: esc_html_e( 'You must be logged in to create new topics.', 'bbpress' );
+				?></li>
+			</ul>
+		</div>
+
+		<?php if ( ! is_user_logged_in() ) : ?>
+
+			<?php bbp_get_template_part( 'form', 'user-login' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+
+<?php endif; ?>
+
+<?php if ( ! bbp_is_single_forum() ) : ?>
+
+</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-edit.php	(revision 7162)
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * bbPress User Profile Edit Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<form id="bbp-your-profile" method="post" enctype="multipart/form-data">
+
+	<h2 class="entry-title"><?php esc_html_e( 'Name', 'bbpress' ) ?></h2>
+
+	<?php do_action( 'bbp_user_edit_before' ); ?>
+
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Name', 'bbpress' ) ?></legend>
+
+		<?php do_action( 'bbp_user_edit_before_name' ); ?>
+
+		<div>
+			<label for="first_name"><?php esc_html_e( 'First Name', 'bbpress' ) ?></label>
+			<input type="text" name="first_name" id="first_name" value="<?php bbp_displayed_user_field( 'first_name', 'edit' ); ?>" class="regular-text" />
+		</div>
+
+		<div>
+			<label for="last_name"><?php esc_html_e( 'Last Name', 'bbpress' ) ?></label>
+			<input type="text" name="last_name" id="last_name" value="<?php bbp_displayed_user_field( 'last_name', 'edit' ); ?>" class="regular-text" />
+		</div>
+
+		<div>
+			<label for="nickname"><?php esc_html_e( 'Nickname', 'bbpress' ); ?></label>
+			<input type="text" name="nickname" id="nickname" value="<?php bbp_displayed_user_field( 'nickname', 'edit' ); ?>" class="regular-text" />
+		</div>
+
+		<div>
+			<label for="display_name"><?php esc_html_e( 'Display Name', 'bbpress' ) ?></label>
+
+			<?php bbp_edit_user_display_name(); ?>
+
+		</div>
+
+		<?php do_action( 'bbp_user_edit_after_name' ); ?>
+
+	</fieldset>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Contact Info', 'bbpress' ) ?></h2>
+
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Contact Info', 'bbpress' ) ?></legend>
+
+		<?php do_action( 'bbp_user_edit_before_contact' ); ?>
+
+		<div>
+			<label for="url"><?php esc_html_e( 'Website', 'bbpress' ) ?></label>
+			<input type="text" name="url" id="url" value="<?php bbp_displayed_user_field( 'user_url', 'edit' ); ?>" maxlength="200" class="regular-text code" />
+		</div>
+
+		<?php foreach ( bbp_edit_user_contact_methods() as $name => $desc ) : ?>
+
+			<div>
+				<label for="<?php echo esc_attr( $name ); ?>"><?php echo apply_filters( 'user_' . $name . '_label', $desc ); ?></label>
+				<input type="text" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $name ); ?>" value="<?php bbp_displayed_user_field( $name, 'edit' ); ?>" class="regular-text" />
+			</div>
+
+		<?php endforeach; ?>
+
+		<?php do_action( 'bbp_user_edit_after_contact' ); ?>
+
+	</fieldset>
+
+	<h2 class="entry-title"><?php bbp_is_user_home_edit()
+		? esc_html_e( 'About Yourself', 'bbpress' )
+		: esc_html_e( 'About the user', 'bbpress' );
+	?></h2>
+
+	<fieldset class="bbp-form">
+		<legend><?php bbp_is_user_home_edit()
+			? esc_html_e( 'About Yourself', 'bbpress' )
+			: esc_html_e( 'About the user', 'bbpress' );
+		?></legend>
+
+		<?php do_action( 'bbp_user_edit_before_about' ); ?>
+
+		<div>
+			<label for="description"><?php esc_html_e( 'Biographical Info', 'bbpress' ); ?></label>
+			<textarea name="description" id="description" rows="5" cols="30"><?php bbp_displayed_user_field( 'description', 'edit' ); ?></textarea>
+		</div>
+
+		<?php do_action( 'bbp_user_edit_after_about' ); ?>
+
+	</fieldset>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Account', 'bbpress' ) ?></h2>
+
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Account', 'bbpress' ) ?></legend>
+
+		<?php do_action( 'bbp_user_edit_before_account' ); ?>
+
+		<div>
+			<label for="user_login"><?php esc_html_e( 'Username', 'bbpress' ); ?></label>
+			<input type="text" name="user_login" id="user_login" value="<?php bbp_displayed_user_field( 'user_login', 'edit' ); ?>" maxlength="100" disabled="disabled" class="regular-text" />
+		</div>
+
+		<div>
+			<label for="email"><?php esc_html_e( 'Email', 'bbpress' ); ?></label>
+			<input type="text" name="email" id="email" value="<?php bbp_displayed_user_field( 'user_email', 'edit' ); ?>" maxlength="100" class="regular-text" autocomplete="off" />
+		</div>
+
+		<?php bbp_get_template_part( 'form', 'user-passwords' ); ?>
+
+		<div>
+			<label for="url"><?php esc_html_e( 'Language', 'bbpress' ) ?></label>
+
+			<?php bbp_edit_user_language(); ?>
+
+		</div>
+
+		<?php do_action( 'bbp_user_edit_after_account' ); ?>
+
+	</fieldset>
+
+	<?php if ( ! bbp_is_user_home_edit() && current_user_can( 'promote_user', bbp_get_displayed_user_id() ) ) : ?>
+
+		<h2 class="entry-title"><?php esc_html_e( 'User Role', 'bbpress' ) ?></h2>
+
+		<fieldset class="bbp-form">
+			<legend><?php esc_html_e( 'User Role', 'bbpress' ); ?></legend>
+
+			<?php do_action( 'bbp_user_edit_before_role' ); ?>
+
+			<?php if ( is_multisite() && is_super_admin() && current_user_can( 'manage_network_options' ) ) : ?>
+
+				<div>
+					<label for="super_admin"><?php esc_html_e( 'Network Role', 'bbpress' ); ?></label>
+					<label>
+						<input class="checkbox" type="checkbox" id="super_admin" name="super_admin"<?php checked( is_super_admin( bbp_get_displayed_user_id() ) ); ?> />
+						<?php esc_html_e( 'Grant this user super admin privileges for the Network.', 'bbpress' ); ?>
+					</label>
+				</div>
+
+			<?php endif; ?>
+
+			<?php bbp_get_template_part( 'form', 'user-roles' ); ?>
+
+			<?php do_action( 'bbp_user_edit_after_role' ); ?>
+
+		</fieldset>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_user_edit_after' ); ?>
+
+	<fieldset class="submit">
+		<legend><?php esc_html_e( 'Save Changes', 'bbpress' ); ?></legend>
+		<div>
+
+			<?php bbp_edit_user_form_fields(); ?>
+
+			<button type="submit" id="bbp_user_edit_submit" name="bbp_user_edit_submit" class="button submit user-submit"><?php bbp_is_user_home_edit()
+				? esc_html_e( 'Update Profile', 'bbpress' )
+				: esc_html_e( 'Update User',    'bbpress' );
+			?></button>
+		</div>
+	</fieldset>
+</form>
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-login.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-login.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-login.php	(revision 7162)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * User Login Form
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<form method="post" action="<?php bbp_wp_login_action( array( 'context' => 'login_post' ) ); ?>" class="bbp-login-form">
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Log In', 'bbpress' ); ?></legend>
+
+		<div class="bbp-username">
+			<label for="user_login"><?php esc_html_e( 'Username', 'bbpress' ); ?>: </label>
+			<input type="text" name="log" value="<?php bbp_sanitize_val( 'user_login', 'text' ); ?>" size="20" maxlength="100" id="user_login" autocomplete="off" />
+		</div>
+
+		<div class="bbp-password">
+			<label for="user_pass"><?php esc_html_e( 'Password', 'bbpress' ); ?>: </label>
+			<input type="password" name="pwd" value="<?php bbp_sanitize_val( 'user_pass', 'password' ); ?>" size="20" id="user_pass" autocomplete="off" />
+		</div>
+
+		<div class="bbp-remember-me">
+			<input type="checkbox" name="rememberme" value="forever" <?php checked( bbp_get_sanitize_val( 'rememberme', 'checkbox' ) ); ?> id="rememberme" />
+			<label for="rememberme"><?php esc_html_e( 'Keep me signed in', 'bbpress' ); ?></label>
+		</div>
+
+		<?php do_action( 'login_form' ); ?>
+
+		<div class="bbp-submit-wrapper">
+
+			<button type="submit" name="user-submit" id="user-submit" class="button submit user-submit"><?php esc_html_e( 'Log In', 'bbpress' ); ?></button>
+
+			<?php bbp_user_login_fields(); ?>
+
+		</div>
+	</fieldset>
+</form>
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-lost-pass.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-lost-pass.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-lost-pass.php	(revision 7162)
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * User Lost Password Form
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<form method="post" action="<?php bbp_wp_login_action( array( 'action' => 'lostpassword', 'context' => 'login_post' ) ); ?>" class="bbp-login-form">
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Lost Password', 'bbpress' ); ?></legend>
+
+		<div class="bbp-username">
+			<p>
+				<label for="user_login" class="hide"><?php esc_html_e( 'Username or Email', 'bbpress' ); ?>: </label>
+				<input type="text" name="user_login" value="" size="20" id="user_login" maxlength="100" autocomplete="off" />
+			</p>
+		</div>
+
+		<?php do_action( 'login_form', 'resetpass' ); ?>
+
+		<div class="bbp-submit-wrapper">
+
+			<button type="submit" name="user-submit" class="button submit user-submit"><?php esc_html_e( 'Reset My Password', 'bbpress' ); ?></button>
+
+			<?php bbp_user_lost_pass_fields(); ?>
+
+		</div>
+	</fieldset>
+</form>
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-passwords.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-passwords.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-passwords.php	(revision 7162)
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * User Password Generator
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Filters the display of the password fields
+if ( apply_filters( 'show_password_fields', true, bbpress()->displayed_user ) ) : ?>
+
+<div id="password" class="user-pass1-wrap">
+	<label for="user_login"><?php esc_html_e( 'Password', 'bbpress' ); ?></label>
+	<button type="button" class="button wp-generate-pw hide-if-no-js"><?php esc_html_e( 'Generate Password', 'bbpress' ); ?></button>
+
+	<fieldset class="bbp-form password wp-pwd hide-if-js">
+		<span class="password-input-wrapper">
+			<input type="password" name="pass1" id="pass1" class="regular-text" value="" autocomplete="off" data-pw="<?php echo esc_attr( wp_generate_password( 24 ) ); ?>" aria-describedby="pass-strength-result" />
+		</span>
+
+		<span class="password-button-wrapper">
+			<button type="button" class="button wp-hide-pw hide-if-no-js" data-toggle="0" aria-label="<?php esc_attr_e( 'Hide password', 'bbpress' ); ?>">
+				<span class="dashicons dashicons-hidden"></span>
+				<span class="text"><?php esc_html_e( 'Hide', 'bbpress' ); ?></span>
+			</button><button type="button" class="button wp-cancel-pw hide-if-no-js" data-toggle="0" aria-label="<?php esc_attr_e( 'Cancel password change', 'bbpress' ); ?>">
+				<span class="dashicons dashicons-no"></span>
+				<span class="text"><?php esc_html_e( 'Cancel', 'bbpress' ); ?></span>
+			</button>
+		</span>
+
+		<div style="display:none" id="pass-strength-result" aria-live="polite"></div>
+	</fieldset>
+</div>
+
+<div class="user-pass2-wrap hide-if-js">
+	<label for="pass2"><?php esc_html_e( 'Repeat New Password', 'bbpress' ); ?></label>
+	<input name="pass2" type="password" id="pass2" class="regular-text" value="" autocomplete="off" />
+	<p class="description"><?php esc_html_e( 'Type your new password again.', 'bbpress' ); ?></p>
+</div>
+
+<div class="pw-weak">
+	<label for="pw_weak"><?php esc_html_e( 'Confirm', 'bbpress' ); ?></label>
+	<input type="checkbox" name="pw_weak" class="pw-checkbox checkbox" />
+	<p class="description indicator-hint"><?php echo wp_get_password_hint(); ?></p>
+</div>
+
+<?php endif;
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-register.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-register.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-register.php	(revision 7162)
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * User Registration Form
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<form method="post" action="<?php bbp_wp_login_action( array( 'context' => 'login_post' ) ); ?>" class="bbp-login-form">
+	<fieldset class="bbp-form">
+		<legend><?php esc_html_e( 'Create an Account', 'bbpress' ); ?></legend>
+
+		<?php do_action( 'bbp_template_before_register_fields' ); ?>
+
+		<div class="bbp-template-notice">
+			<ul>
+				<li><?php esc_html_e( 'Your username must be unique, and cannot be changed later.',                        'bbpress' ); ?></li>
+				<li><?php esc_html_e( 'We use your email address to email you a secure password and verify your account.', 'bbpress' ); ?></li>
+			</ul>
+		</div>
+
+		<div class="bbp-username">
+			<label for="user_login"><?php esc_html_e( 'Username', 'bbpress' ); ?>: </label>
+			<input type="text" name="user_login" value="<?php bbp_sanitize_val( 'user_login' ); ?>" size="20" id="user_login" maxlength="100" autocomplete="off" />
+		</div>
+
+		<div class="bbp-email">
+			<label for="user_email"><?php esc_html_e( 'Email', 'bbpress' ); ?>: </label>
+			<input type="text" name="user_email" value="<?php bbp_sanitize_val( 'user_email' ); ?>" size="20" id="user_email" maxlength="100" autocomplete="off" />
+		</div>
+
+		<?php do_action( 'register_form' ); ?>
+
+		<div class="bbp-submit-wrapper">
+
+			<button type="submit" name="user-submit" class="button submit user-submit"><?php esc_html_e( 'Register', 'bbpress' ); ?></button>
+
+			<?php bbp_user_register_fields(); ?>
+
+		</div>
+
+		<?php do_action( 'bbp_template_after_register_fields' ); ?>
+
+	</fieldset>
+</form>
Index: /tags/2.6.6/src/templates/default/bbpress/form-user-roles.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/form-user-roles.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/form-user-roles.php	(revision 7162)
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * User Roles Profile Edit Part
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div>
+	<label for="role"><?php esc_html_e( 'Blog Role', 'bbpress' ) ?></label>
+
+	<?php bbp_edit_user_blog_role(); ?>
+
+</div>
+
+<div>
+	<label for="forum-role"><?php esc_html_e( 'Forum Role', 'bbpress' ) ?></label>
+
+	<?php bbp_edit_user_forums_role(); ?>
+
+</div>
Index: /tags/2.6.6/src/templates/default/bbpress/loop-forums.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-forums.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-forums.php	(revision 7162)
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Forums Loop
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_forums_loop' ); ?>
+
+<ul id="forums-list-<?php bbp_forum_id(); ?>" class="bbp-forums">
+
+	<li class="bbp-header">
+
+		<ul class="forum-titles">
+			<li class="bbp-forum-info"><?php esc_html_e( 'Forum', 'bbpress' ); ?></li>
+			<li class="bbp-forum-topic-count"><?php esc_html_e( 'Topics', 'bbpress' ); ?></li>
+			<li class="bbp-forum-reply-count"><?php bbp_show_lead_topic()
+				? esc_html_e( 'Replies', 'bbpress' )
+				: esc_html_e( 'Posts',   'bbpress' );
+			?></li>
+			<li class="bbp-forum-freshness"><?php esc_html_e( 'Last Post', 'bbpress' ); ?></li>
+		</ul>
+
+	</li><!-- .bbp-header -->
+
+	<li class="bbp-body">
+
+		<?php while ( bbp_forums() ) : bbp_the_forum(); ?>
+
+			<?php bbp_get_template_part( 'loop', 'single-forum' ); ?>
+
+		<?php endwhile; ?>
+
+	</li><!-- .bbp-body -->
+
+	<li class="bbp-footer">
+
+		<div class="tr">
+			<p class="td colspan4">&nbsp;</p>
+		</div><!-- .tr -->
+
+	</li><!-- .bbp-footer -->
+
+</ul><!-- .forums-directory -->
+
+<?php do_action( 'bbp_template_after_forums_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/loop-replies.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-replies.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-replies.php	(revision 7162)
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Replies Loop
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_replies_loop' ); ?>
+
+<ul id="topic-<?php bbp_topic_id(); ?>-replies" class="forums bbp-replies">
+
+	<li class="bbp-header">
+		<div class="bbp-reply-author"><?php esc_html_e( 'Author',  'bbpress' ); ?></div><!-- .bbp-reply-author -->
+		<div class="bbp-reply-content"><?php bbp_show_lead_topic()
+			? esc_html_e( 'Replies', 'bbpress' )
+			: esc_html_e( 'Posts',   'bbpress' );
+		?></div><!-- .bbp-reply-content -->
+	</li><!-- .bbp-header -->
+
+	<li class="bbp-body">
+
+		<?php if ( bbp_thread_replies() ) : ?>
+
+			<?php bbp_list_replies(); ?>
+
+		<?php else : ?>
+
+			<?php while ( bbp_replies() ) : bbp_the_reply(); ?>
+
+				<?php bbp_get_template_part( 'loop', 'single-reply' ); ?>
+
+			<?php endwhile; ?>
+
+		<?php endif; ?>
+
+	</li><!-- .bbp-body -->
+
+	<li class="bbp-footer">
+		<div class="bbp-reply-author"><?php esc_html_e( 'Author',  'bbpress' ); ?></div>
+		<div class="bbp-reply-content"><?php bbp_show_lead_topic()
+			? esc_html_e( 'Replies', 'bbpress' )
+			: esc_html_e( 'Posts',   'bbpress' );
+		?></div><!-- .bbp-reply-content -->
+	</li><!-- .bbp-footer -->
+</ul><!-- #topic-<?php bbp_topic_id(); ?>-replies -->
+
+<?php do_action( 'bbp_template_after_replies_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/loop-search-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-search-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-search-forum.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Search Loop - Single Forum
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-forum-header">
+	<div class="bbp-meta">
+		<span class="bbp-forum-post-date"><?php printf( esc_html__( 'Last updated %s', 'bbpress' ), bbp_get_forum_last_active_time() ); ?></span>
+		<a href="<?php bbp_forum_permalink(); ?>" class="bbp-forum-permalink">#<?php bbp_forum_id(); ?></a>
+	</div><!-- .bbp-meta -->
+
+	<div class="bbp-forum-title">
+
+		<?php do_action( 'bbp_theme_before_forum_title' ); ?>
+
+		<h3><?php esc_html_e( 'Forum:', 'bbpress' ); ?>
+		<a href="<?php bbp_forum_permalink(); ?>"><?php bbp_forum_title(); ?></a></h3>
+
+		<?php do_action( 'bbp_theme_after_forum_title' ); ?>
+
+	</div><!-- .bbp-forum-title -->
+</div><!-- .bbp-forum-header -->
+
+<div id="post-<?php bbp_forum_id(); ?>" <?php bbp_forum_class(); ?>>
+	<div class="bbp-forum-content">
+
+		<?php do_action( 'bbp_theme_before_forum_content' ); ?>
+
+		<?php bbp_forum_content(); ?>
+
+		<?php do_action( 'bbp_theme_after_forum_content' ); ?>
+
+	</div><!-- .bbp-forum-content -->
+</div><!-- #post-<?php bbp_forum_id(); ?> -->
Index: /tags/2.6.6/src/templates/default/bbpress/loop-search-reply.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-search-reply.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-search-reply.php	(revision 7162)
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Search Loop - Single Reply
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-reply-header">
+	<div class="bbp-meta">
+		<span class="bbp-reply-post-date"><?php bbp_reply_post_date(); ?></span>
+		<a href="<?php bbp_reply_url(); ?>" class="bbp-reply-permalink">#<?php bbp_reply_id(); ?></a>
+	</div><!-- .bbp-meta -->
+
+	<div class="bbp-reply-title">
+		<h3><?php esc_html_e( 'In reply to: ', 'bbpress' ); ?>
+		<a class="bbp-topic-permalink" href="<?php bbp_topic_permalink( bbp_get_reply_topic_id() ); ?>"><?php bbp_topic_title( bbp_get_reply_topic_id() ); ?></a></h3>
+	</div><!-- .bbp-reply-title -->
+</div><!-- .bbp-reply-header -->
+
+<div id="post-<?php bbp_reply_id(); ?>" <?php bbp_reply_class(); ?>>
+	<div class="bbp-reply-author">
+
+		<?php do_action( 'bbp_theme_before_reply_author_details' ); ?>
+
+		<?php bbp_reply_author_link( array( 'show_role' => true ) ); ?>
+
+		<?php if ( bbp_is_user_keymaster() ) : ?>
+
+			<?php do_action( 'bbp_theme_before_reply_author_admin_details' ); ?>
+
+			<div class="bbp-reply-ip"><?php bbp_author_ip( bbp_get_reply_id() ); ?></div>
+
+			<?php do_action( 'bbp_theme_after_reply_author_admin_details' ); ?>
+
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_theme_after_reply_author_details' ); ?>
+
+	</div><!-- .bbp-reply-author -->
+
+	<div class="bbp-reply-content">
+
+		<?php do_action( 'bbp_theme_before_reply_content' ); ?>
+
+		<?php bbp_reply_content(); ?>
+
+		<?php do_action( 'bbp_theme_after_reply_content' ); ?>
+
+	</div><!-- .bbp-reply-content -->
+</div><!-- #post-<?php bbp_reply_id(); ?> -->
+
Index: /tags/2.6.6/src/templates/default/bbpress/loop-search-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-search-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-search-topic.php	(revision 7162)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Search Loop - Single Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div class="bbp-topic-header">
+	<div class="bbp-meta">
+		<span class="bbp-topic-post-date"><?php bbp_topic_post_date( bbp_get_topic_id() ); ?></span>
+		<a href="<?php bbp_topic_permalink(); ?>" class="bbp-topic-permalink">#<?php bbp_topic_id(); ?></a>
+	</div><!-- .bbp-meta -->
+
+	<div class="bbp-topic-title">
+
+		<?php do_action( 'bbp_theme_before_topic_title' ); ?>
+
+		<h3><?php esc_html_e( 'Topic:', 'bbpress' ); ?>
+		<a href="<?php bbp_topic_permalink(); ?>"><?php bbp_topic_title(); ?></a></h3>
+
+		<div class="bbp-topic-title-meta">
+
+			<?php if ( function_exists( 'bbp_is_forum_group_forum' ) && bbp_is_forum_group_forum( bbp_get_topic_forum_id() ) ) : ?>
+
+				<?php esc_html_e( 'in group forum ', 'bbpress' ); ?>
+
+			<?php else : ?>
+
+				<?php esc_html_e( 'in forum ', 'bbpress' ); ?>
+
+			<?php endif; ?>
+
+			<a href="<?php bbp_forum_permalink( bbp_get_topic_forum_id() ); ?>"><?php bbp_forum_title( bbp_get_topic_forum_id() ); ?></a>
+
+		</div><!-- .bbp-topic-title-meta -->
+
+		<?php do_action( 'bbp_theme_after_topic_title' ); ?>
+
+	</div><!-- .bbp-topic-title -->
+
+</div><!-- .bbp-topic-header -->
+
+<div id="post-<?php bbp_topic_id(); ?>" <?php bbp_topic_class(); ?>>
+	<div class="bbp-topic-author">
+
+		<?php do_action( 'bbp_theme_before_topic_author_details' ); ?>
+
+		<?php bbp_topic_author_link( array( 'show_role' => true ) ); ?>
+
+		<?php if ( bbp_is_user_keymaster() ) : ?>
+
+			<?php do_action( 'bbp_theme_before_topic_author_admin_details' ); ?>
+
+			<div class="bbp-reply-ip"><?php bbp_author_ip( bbp_get_topic_id() ); ?></div>
+
+			<?php do_action( 'bbp_theme_after_topic_author_admin_details' ); ?>
+
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_theme_after_topic_author_details' ); ?>
+
+	</div><!-- .bbp-topic-author -->
+
+	<div class="bbp-topic-content">
+
+		<?php do_action( 'bbp_theme_before_topic_content' ); ?>
+
+		<?php bbp_topic_content(); ?>
+
+		<?php do_action( 'bbp_theme_after_topic_content' ); ?>
+
+	</div><!-- .bbp-topic-content -->
+</div><!-- #post-<?php bbp_topic_id(); ?> -->
Index: /tags/2.6.6/src/templates/default/bbpress/loop-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-search.php	(revision 7162)
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Search Loop
+ *
+ * @package bbPress
+ * @subpackage Theme
+*/
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_search_results_loop' ); ?>
+
+<ul id="bbp-search-results" class="forums bbp-search-results">
+
+	<li class="bbp-header">
+
+		<div class="bbp-search-author"><?php esc_html_e( 'Author',  'bbpress' ); ?></div><!-- .bbp-reply-author -->
+
+		<div class="bbp-search-content">
+
+			<?php esc_html_e( 'Search Results', 'bbpress' ); ?>
+
+		</div><!-- .bbp-search-content -->
+
+	</li><!-- .bbp-header -->
+
+	<li class="bbp-body">
+
+		<?php while ( bbp_search_results() ) : bbp_the_search_result(); ?>
+
+			<?php bbp_get_template_part( 'loop', 'search-' . get_post_type() ); ?>
+
+		<?php endwhile; ?>
+
+	</li><!-- .bbp-body -->
+
+	<li class="bbp-footer">
+
+		<div class="bbp-search-author"><?php esc_html_e( 'Author',  'bbpress' ); ?></div>
+
+		<div class="bbp-search-content">
+
+			<?php esc_html_e( 'Search Results', 'bbpress' ); ?>
+
+		</div><!-- .bbp-search-content -->
+
+	</li><!-- .bbp-footer -->
+
+</ul><!-- #bbp-search-results -->
+
+<?php do_action( 'bbp_template_after_search_results_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/loop-single-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-single-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-single-forum.php	(revision 7162)
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Forums Loop - Single Forum
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<ul id="bbp-forum-<?php bbp_forum_id(); ?>" <?php bbp_forum_class(); ?>>
+	<li class="bbp-forum-info">
+
+		<?php if ( bbp_is_user_home() && bbp_is_subscriptions() ) : ?>
+
+			<span class="bbp-row-actions">
+
+				<?php do_action( 'bbp_theme_before_forum_subscription_action' ); ?>
+
+				<?php bbp_forum_subscription_link( array( 'before' => '', 'subscribe' => '+', 'unsubscribe' => '&times;' ) ); ?>
+
+				<?php do_action( 'bbp_theme_after_forum_subscription_action' ); ?>
+
+			</span>
+
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_theme_before_forum_title' ); ?>
+
+		<a class="bbp-forum-title" href="<?php bbp_forum_permalink(); ?>"><?php bbp_forum_title(); ?></a>
+
+		<?php do_action( 'bbp_theme_after_forum_title' ); ?>
+
+		<?php do_action( 'bbp_theme_before_forum_description' ); ?>
+
+		<div class="bbp-forum-content"><?php bbp_forum_content(); ?></div>
+
+		<?php do_action( 'bbp_theme_after_forum_description' ); ?>
+
+		<?php do_action( 'bbp_theme_before_forum_sub_forums' ); ?>
+
+		<?php bbp_list_forums(); ?>
+
+		<?php do_action( 'bbp_theme_after_forum_sub_forums' ); ?>
+
+		<?php bbp_forum_row_actions(); ?>
+
+	</li>
+
+	<li class="bbp-forum-topic-count"><?php bbp_forum_topic_count(); ?></li>
+
+	<li class="bbp-forum-reply-count"><?php bbp_show_lead_topic() ? bbp_forum_reply_count() : bbp_forum_post_count(); ?></li>
+
+	<li class="bbp-forum-freshness">
+
+		<?php do_action( 'bbp_theme_before_forum_freshness_link' ); ?>
+
+		<?php bbp_forum_freshness_link(); ?>
+
+		<?php do_action( 'bbp_theme_after_forum_freshness_link' ); ?>
+
+		<p class="bbp-topic-meta">
+
+			<?php do_action( 'bbp_theme_before_topic_author' ); ?>
+
+			<span class="bbp-topic-freshness-author"><?php bbp_author_link( array( 'post_id' => bbp_get_forum_last_active_id(), 'size' => 14 ) ); ?></span>
+
+			<?php do_action( 'bbp_theme_after_topic_author' ); ?>
+
+		</p>
+	</li>
+</ul><!-- #bbp-forum-<?php bbp_forum_id(); ?> -->
Index: /tags/2.6.6/src/templates/default/bbpress/loop-single-reply.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-single-reply.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-single-reply.php	(revision 7162)
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Replies Loop - Single Reply
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<div id="post-<?php bbp_reply_id(); ?>" class="bbp-reply-header">
+	<div class="bbp-meta">
+		<span class="bbp-reply-post-date"><?php bbp_reply_post_date(); ?></span>
+
+		<?php if ( bbp_is_single_user_replies() ) : ?>
+
+			<span class="bbp-header">
+				<?php esc_html_e( 'in reply to: ', 'bbpress' ); ?>
+				<a class="bbp-topic-permalink" href="<?php bbp_topic_permalink( bbp_get_reply_topic_id() ); ?>"><?php bbp_topic_title( bbp_get_reply_topic_id() ); ?></a>
+			</span>
+
+		<?php endif; ?>
+
+		<a href="<?php bbp_reply_url(); ?>" class="bbp-reply-permalink">#<?php bbp_reply_id(); ?></a>
+
+		<?php do_action( 'bbp_theme_before_reply_admin_links' ); ?>
+
+		<?php bbp_reply_admin_links(); ?>
+
+		<?php do_action( 'bbp_theme_after_reply_admin_links' ); ?>
+
+	</div><!-- .bbp-meta -->
+</div><!-- #post-<?php bbp_reply_id(); ?> -->
+
+<div <?php bbp_reply_class(); ?>>
+	<div class="bbp-reply-author">
+
+		<?php do_action( 'bbp_theme_before_reply_author_details' ); ?>
+
+		<?php bbp_reply_author_link( array( 'show_role' => true ) ); ?>
+
+		<?php if ( current_user_can( 'moderate', bbp_get_reply_id() ) ) : ?>
+
+			<?php do_action( 'bbp_theme_before_reply_author_admin_details' ); ?>
+
+			<div class="bbp-reply-ip"><?php bbp_author_ip( bbp_get_reply_id() ); ?></div>
+
+			<?php do_action( 'bbp_theme_after_reply_author_admin_details' ); ?>
+
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_theme_after_reply_author_details' ); ?>
+
+	</div><!-- .bbp-reply-author -->
+
+	<div class="bbp-reply-content">
+
+		<?php do_action( 'bbp_theme_before_reply_content' ); ?>
+
+		<?php bbp_reply_content(); ?>
+
+		<?php do_action( 'bbp_theme_after_reply_content' ); ?>
+
+	</div><!-- .bbp-reply-content -->
+</div><!-- .reply -->
Index: /tags/2.6.6/src/templates/default/bbpress/loop-single-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-single-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-single-topic.php	(revision 7162)
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Topics Loop - Single
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+?>
+
+<ul id="bbp-topic-<?php bbp_topic_id(); ?>" <?php bbp_topic_class(); ?>>
+	<li class="bbp-topic-title">
+
+		<?php if ( bbp_is_user_home() ) : ?>
+
+			<?php if ( bbp_is_favorites() ) : ?>
+
+				<span class="bbp-row-actions">
+
+					<?php do_action( 'bbp_theme_before_topic_favorites_action' ); ?>
+
+					<?php bbp_topic_favorite_link( array( 'before' => '', 'favorite' => '+', 'favorited' => '&times;' ) ); ?>
+
+					<?php do_action( 'bbp_theme_after_topic_favorites_action' ); ?>
+
+				</span>
+
+			<?php elseif ( bbp_is_subscriptions() ) : ?>
+
+				<span class="bbp-row-actions">
+
+					<?php do_action( 'bbp_theme_before_topic_subscription_action' ); ?>
+
+					<?php bbp_topic_subscription_link( array( 'before' => '', 'subscribe' => '+', 'unsubscribe' => '&times;' ) ); ?>
+
+					<?php do_action( 'bbp_theme_after_topic_subscription_action' ); ?>
+
+				</span>
+
+			<?php endif; ?>
+
+		<?php endif; ?>
+
+		<?php do_action( 'bbp_theme_before_topic_title' ); ?>
+
+		<a class="bbp-topic-permalink" href="<?php bbp_topic_permalink(); ?>"><?php bbp_topic_title(); ?></a>
+
+		<?php do_action( 'bbp_theme_after_topic_title' ); ?>
+
+		<?php bbp_topic_pagination(); ?>
+
+		<?php do_action( 'bbp_theme_before_topic_meta' ); ?>
+
+		<p class="bbp-topic-meta">
+
+			<?php do_action( 'bbp_theme_before_topic_started_by' ); ?>
+
+			<span class="bbp-topic-started-by"><?php printf( esc_html__( 'Started by: %1$s', 'bbpress' ), bbp_get_topic_author_link( array( 'size' => '14' ) ) ); ?></span>
+
+			<?php do_action( 'bbp_theme_after_topic_started_by' ); ?>
+
+			<?php if ( ! bbp_is_single_forum() || ( bbp_get_topic_forum_id() !== bbp_get_forum_id() ) ) : ?>
+
+				<?php do_action( 'bbp_theme_before_topic_started_in' ); ?>
+
+				<span class="bbp-topic-started-in"><?php printf( esc_html__( 'in: %1$s', 'bbpress' ), '<a href="' . bbp_get_forum_permalink( bbp_get_topic_forum_id() ) . '">' . bbp_get_forum_title( bbp_get_topic_forum_id() ) . '</a>' ); ?></span>
+				<?php do_action( 'bbp_theme_after_topic_started_in' ); ?>
+
+			<?php endif; ?>
+
+		</p>
+
+		<?php do_action( 'bbp_theme_after_topic_meta' ); ?>
+
+		<?php bbp_topic_row_actions(); ?>
+
+	</li>
+
+	<li class="bbp-topic-voice-count"><?php bbp_topic_voice_count(); ?></li>
+
+	<li class="bbp-topic-reply-count"><?php bbp_show_lead_topic() ? bbp_topic_reply_count() : bbp_topic_post_count(); ?></li>
+
+	<li class="bbp-topic-freshness">
+
+		<?php do_action( 'bbp_theme_before_topic_freshness_link' ); ?>
+
+		<?php bbp_topic_freshness_link(); ?>
+
+		<?php do_action( 'bbp_theme_after_topic_freshness_link' ); ?>
+
+		<p class="bbp-topic-meta">
+
+			<?php do_action( 'bbp_theme_before_topic_freshness_author' ); ?>
+
+			<span class="bbp-topic-freshness-author"><?php bbp_author_link( array( 'post_id' => bbp_get_topic_last_active_id(), 'size' => 14 ) ); ?></span>
+
+			<?php do_action( 'bbp_theme_after_topic_freshness_author' ); ?>
+
+		</p>
+	</li>
+</ul><!-- #bbp-topic-<?php bbp_topic_id(); ?> -->
Index: /tags/2.6.6/src/templates/default/bbpress/loop-topics.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/loop-topics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/loop-topics.php	(revision 7162)
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Topics Loop
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_topics_loop' ); ?>
+
+<ul id="bbp-forum-<?php bbp_forum_id(); ?>" class="bbp-topics">
+	<li class="bbp-header">
+		<ul class="forum-titles">
+			<li class="bbp-topic-title"><?php esc_html_e( 'Topic', 'bbpress' ); ?></li>
+			<li class="bbp-topic-voice-count"><?php esc_html_e( 'Voices', 'bbpress' ); ?></li>
+			<li class="bbp-topic-reply-count"><?php bbp_show_lead_topic()
+				? esc_html_e( 'Replies', 'bbpress' )
+				: esc_html_e( 'Posts',   'bbpress' );
+			?></li>
+			<li class="bbp-topic-freshness"><?php esc_html_e( 'Last Post', 'bbpress' ); ?></li>
+		</ul>
+	</li>
+
+	<li class="bbp-body">
+
+		<?php while ( bbp_topics() ) : bbp_the_topic(); ?>
+
+			<?php bbp_get_template_part( 'loop', 'single-topic' ); ?>
+
+		<?php endwhile; ?>
+
+	</li>
+
+	<li class="bbp-footer">
+		<div class="tr">
+			<p>
+				<span class="td colspan<?php echo ( bbp_is_user_home() && ( bbp_is_favorites() || bbp_is_subscriptions() ) ) ? '5' : '4'; ?>">&nbsp;</span>
+			</p>
+		</div><!-- .tr -->
+	</li>
+</ul><!-- #bbp-forum-<?php bbp_forum_id(); ?> -->
+
+<?php do_action( 'bbp_template_after_topics_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/pagination-replies.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/pagination-replies.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/pagination-replies.php	(revision 7162)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Pagination for pages of replies (when viewing a topic)
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_pagination_loop' ); ?>
+
+<div class="bbp-pagination">
+	<div class="bbp-pagination-count"><?php bbp_topic_pagination_count(); ?></div>
+	<div class="bbp-pagination-links"><?php bbp_topic_pagination_links(); ?></div>
+</div>
+
+<?php do_action( 'bbp_template_after_pagination_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/pagination-search.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/pagination-search.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/pagination-search.php	(revision 7162)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Pagination for pages of search results 
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_pagination_loop' ); ?>
+
+<div class="bbp-pagination">
+	<div class="bbp-pagination-count"><?php bbp_search_pagination_count(); ?></div>
+	<div class="bbp-pagination-links"><?php bbp_search_pagination_links(); ?></div>
+</div>
+
+<?php do_action( 'bbp_template_after_pagination_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/pagination-topics.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/pagination-topics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/pagination-topics.php	(revision 7162)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Pagination for pages of topics (when viewing a forum)
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_pagination_loop' ); ?>
+
+<div class="bbp-pagination">
+	<div class="bbp-pagination-count"><?php bbp_forum_pagination_count(); ?></div>
+	<div class="bbp-pagination-links"><?php bbp_forum_pagination_links(); ?></div>
+</div>
+
+<?php do_action( 'bbp_template_after_pagination_loop' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-details.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-details.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-details.php	(revision 7162)
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * User Details
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_details' ); ?>
+
+<div id="bbp-single-user-details">
+	<div id="bbp-user-avatar">
+		<span class='vcard'>
+			<a class="url fn n" href="<?php bbp_user_profile_url(); ?>" title="<?php bbp_displayed_user_field( 'display_name' ); ?>" rel="me">
+				<?php echo get_avatar( bbp_get_displayed_user_field( 'user_email', 'raw' ), apply_filters( 'bbp_single_user_details_avatar_size', 150 ) ); ?>
+			</a>
+		</span>
+	</div>
+
+	<?php do_action( 'bbp_template_before_user_details_menu_items' ); ?>
+
+	<div id="bbp-user-navigation">
+		<ul>
+			<li class="<?php if ( bbp_is_single_user_profile() ) :?>current<?php endif; ?>">
+				<span class="vcard bbp-user-profile-link">
+					<a class="url fn n" href="<?php bbp_user_profile_url(); ?>" title="<?php printf( esc_attr__( "%s's Profile", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>" rel="me"><?php esc_html_e( 'Profile', 'bbpress' ); ?></a>
+				</span>
+			</li>
+
+			<li class="<?php if ( bbp_is_single_user_topics() ) :?>current<?php endif; ?>">
+				<span class='bbp-user-topics-created-link'>
+					<a href="<?php bbp_user_topics_created_url(); ?>" title="<?php printf( esc_attr__( "%s's Topics Started", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Topics Started', 'bbpress' ); ?></a>
+				</span>
+			</li>
+
+			<li class="<?php if ( bbp_is_single_user_replies() ) :?>current<?php endif; ?>">
+				<span class='bbp-user-replies-created-link'>
+					<a href="<?php bbp_user_replies_created_url(); ?>" title="<?php printf( esc_attr__( "%s's Replies Created", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Replies Created', 'bbpress' ); ?></a>
+				</span>
+			</li>
+
+			<?php if ( bbp_is_engagements_active() ) : ?>
+				<li class="<?php if ( bbp_is_single_user_engagements() ) :?>current<?php endif; ?>">
+					<span class='bbp-user-engagements-created-link'>
+						<a href="<?php bbp_user_engagements_url(); ?>" title="<?php printf( esc_attr__( "%s's Engagements", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Engagements', 'bbpress' ); ?></a>
+					</span>
+				</li>
+			<?php endif; ?>
+
+			<?php if ( bbp_is_favorites_active() ) : ?>
+				<li class="<?php if ( bbp_is_favorites() ) :?>current<?php endif; ?>">
+					<span class="bbp-user-favorites-link">
+						<a href="<?php bbp_favorites_permalink(); ?>" title="<?php printf( esc_attr__( "%s's Favorites", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Favorites', 'bbpress' ); ?></a>
+					</span>
+				</li>
+			<?php endif; ?>
+
+			<?php if ( bbp_is_user_home() || current_user_can( 'edit_user', bbp_get_displayed_user_id() ) ) : ?>
+
+				<?php if ( bbp_is_subscriptions_active() ) : ?>
+					<li class="<?php if ( bbp_is_subscriptions() ) :?>current<?php endif; ?>">
+						<span class="bbp-user-subscriptions-link">
+							<a href="<?php bbp_subscriptions_permalink(); ?>" title="<?php printf( esc_attr__( "%s's Subscriptions", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Subscriptions', 'bbpress' ); ?></a>
+						</span>
+					</li>
+				<?php endif; ?>
+
+				<li class="<?php if ( bbp_is_single_user_edit() ) :?>current<?php endif; ?>">
+					<span class="bbp-user-edit-link">
+						<a href="<?php bbp_user_profile_edit_url(); ?>" title="<?php printf( esc_attr__( "Edit %s's Profile", 'bbpress' ), bbp_get_displayed_user_field( 'display_name' ) ); ?>"><?php esc_html_e( 'Edit', 'bbpress' ); ?></a>
+					</span>
+				</li>
+
+			<?php endif; ?>
+
+		</ul>
+
+		<?php do_action( 'bbp_template_after_user_details_menu_items' ); ?>
+
+	</div>
+</div>
+
+<?php do_action( 'bbp_template_after_user_details' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-engagements.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-engagements.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-engagements.php	(revision 7162)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * User Engagements
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_engagements' ); ?>
+
+<div id="bbp-user-engagements" class="bbp-user-engagements">
+
+	<?php bbp_get_template_part( 'form', 'topic-search' ); ?>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Topics Engaged In', 'bbpress' ); ?></h2>
+	<div class="bbp-user-section">
+
+		<?php if ( bbp_get_user_engagements() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'topics' ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+		<?php else : ?>
+
+			<?php bbp_get_template_part( 'feedback', 'no-topics' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+</div><!-- #bbp-user-engagements -->
+
+<?php do_action( 'bbp_template_after_user_engagements' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-favorites.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-favorites.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-favorites.php	(revision 7162)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * User Favorites
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_favorites' ); ?>
+
+<div id="bbp-user-favorites" class="bbp-user-favorites">
+
+	<?php bbp_get_template_part( 'form', 'topic-search' ); ?>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Favorite Forum Topics', 'bbpress' ); ?></h2>
+	<div class="bbp-user-section">
+
+		<?php if ( bbp_get_user_favorites() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'topics' ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+		<?php else : ?>
+
+			<?php bbp_get_template_part( 'feedback', 'no-topics' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+</div><!-- #bbp-user-favorites -->
+
+<?php do_action( 'bbp_template_after_user_favorites' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-profile.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-profile.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-profile.php	(revision 7162)
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * User Profile
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_profile' ); ?>
+
+<div id="bbp-user-profile" class="bbp-user-profile">
+	<h2 class="entry-title">@<?php bbp_displayed_user_field( 'user_nicename' ); ?></h2>
+	<div class="bbp-user-section">
+		<h3><?php esc_html_e( 'Profile', 'bbpress' ); ?></h3>
+		<p class="bbp-user-forum-role"><?php  printf( esc_html__( 'Registered: %s', 'bbpress' ), bbp_get_time_since( bbp_get_displayed_user_field( 'user_registered' ) ) ); ?></p>
+
+		<?php if ( bbp_get_displayed_user_field( 'description' ) ) : ?>
+
+			<p class="bbp-user-description"><?php echo bbp_rel_nofollow( bbp_get_displayed_user_field( 'description' ) ); ?></p>
+
+		<?php endif; ?>
+
+		<?php if ( bbp_get_displayed_user_field( 'user_url' ) ) : ?>
+
+			<p class="bbp-user-website"><?php  printf( esc_html__( 'Website: %s', 'bbpress' ), bbp_rel_nofollow( bbp_make_clickable( bbp_get_displayed_user_field( 'user_url' ) ) ) ); ?></p>
+
+		<?php endif; ?>
+
+		<hr>
+		<h3><?php esc_html_e( 'Forums', 'bbpress' ); ?></h3>
+
+		<?php if ( bbp_get_user_last_posted() ) : ?>
+
+			<p class="bbp-user-last-activity"><?php printf( esc_html__( 'Last Activity: %s',  'bbpress' ), bbp_get_time_since( bbp_get_user_last_posted(), false, true ) ); ?></p>
+
+		<?php endif; ?>
+
+		<p class="bbp-user-topic-count"><?php printf( esc_html__( 'Topics Started: %s',  'bbpress' ), bbp_get_user_topic_count() ); ?></p>
+		<p class="bbp-user-reply-count"><?php printf( esc_html__( 'Replies Created: %s', 'bbpress' ), bbp_get_user_reply_count() ); ?></p>
+		<p class="bbp-user-forum-role"><?php  printf( esc_html__( 'Forum Role: %s',      'bbpress' ), bbp_get_user_display_role() ); ?></p>
+	</div>
+</div><!-- #bbp-author-topics-started -->
+
+<?php do_action( 'bbp_template_after_user_profile' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-replies-created.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-replies-created.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-replies-created.php	(revision 7162)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * User Replies Created
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_replies' ); ?>
+
+<div id="bbp-user-replies-created" class="bbp-user-replies-created">
+
+	<?php bbp_get_template_part( 'form', 'reply-search' ); ?>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Forum Replies Created', 'bbpress' ); ?></h2>
+	<div class="bbp-user-section">
+
+		<?php if ( bbp_get_user_replies_created() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'replies' ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'replies' ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'replies' ); ?>
+
+		<?php else : ?>
+
+			<?php bbp_get_template_part( 'feedback', 'no-replies' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+</div><!-- #bbp-user-replies-created -->
+
+<?php do_action( 'bbp_template_after_user_replies' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-subscriptions.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-subscriptions.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-subscriptions.php	(revision 7162)
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * User Subscriptions
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_subscriptions' ); ?>
+
+<?php if ( bbp_is_subscriptions_active() ) : ?>
+
+	<?php if ( bbp_is_user_home() || current_user_can( 'edit_user', bbp_get_displayed_user_id() ) ) : ?>
+
+		<div id="bbp-user-subscriptions" class="bbp-user-subscriptions">
+
+			<?php bbp_get_template_part( 'form', 'topic-search' ); ?>
+
+			<h2 class="entry-title"><?php esc_html_e( 'Subscribed Forums', 'bbpress' ); ?></h2>
+			<div class="bbp-user-section">
+
+				<?php if ( bbp_get_user_forum_subscriptions() ) : ?>
+
+					<?php bbp_get_template_part( 'loop', 'forums' ); ?>
+
+				<?php else : ?>
+
+					<?php bbp_get_template_part( 'feedback', 'no-forums' ); ?>
+
+				<?php endif; ?>
+
+			</div>
+
+			<h2 class="entry-title"><?php esc_html_e( 'Subscribed Topics', 'bbpress' ); ?></h2>
+			<div class="bbp-user-section">
+
+				<?php if ( bbp_get_user_topic_subscriptions() ) : ?>
+
+					<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+					<?php bbp_get_template_part( 'loop',       'topics' ); ?>
+
+					<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+				<?php else : ?>
+
+					<?php bbp_get_template_part( 'feedback', 'no-topics' ); ?>
+
+				<?php endif; ?>
+
+			</div>
+		</div><!-- #bbp-user-subscriptions -->
+
+	<?php endif; ?>
+
+<?php endif; ?>
+
+<?php do_action( 'bbp_template_after_user_subscriptions' );
Index: /tags/2.6.6/src/templates/default/bbpress/user-topics-created.php
===================================================================
--- /tags/2.6.6/src/templates/default/bbpress/user-topics-created.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/bbpress/user-topics-created.php	(revision 7162)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * User Topics Created
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+do_action( 'bbp_template_before_user_topics_created' ); ?>
+
+<div id="bbp-user-topics-started" class="bbp-user-topics-started">
+
+	<?php bbp_get_template_part( 'form', 'topic-search' ); ?>
+
+	<h2 class="entry-title"><?php esc_html_e( 'Forum Topics Started', 'bbpress' ); ?></h2>
+	<div class="bbp-user-section">
+
+		<?php if ( bbp_get_user_topics_started() ) : ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+			<?php bbp_get_template_part( 'loop',       'topics' ); ?>
+
+			<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+		<?php else : ?>
+
+			<?php bbp_get_template_part( 'feedback', 'no-topics' ); ?>
+
+		<?php endif; ?>
+
+	</div>
+</div><!-- #bbp-user-topics-started -->
+
+<?php do_action( 'bbp_template_after_user_topics_created' );
Index: /tags/2.6.6/src/templates/default/css/bbpress.css
===================================================================
--- /tags/2.6.6/src/templates/default/css/bbpress.css	(revision 7162)
+++ /tags/2.6.6/src/templates/default/css/bbpress.css	(revision 7162)
@@ -0,0 +1,1701 @@
+/**
+ * bbPress specific CSS
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+/* =bbPress Style
+-------------------------------------------------------------- */
+
+.hidden,
+.js .hide-if-js {
+	display: none;
+}
+
+/* Hide visually but not from screen readers */
+.screen-reader-text,
+.screen-reader-text span,
+.ui-helper-hidden-accessible {
+	position: absolute;
+	margin: -1px;
+	padding: 0;
+	height: 1px;
+	width: 1px;
+	overflow: hidden;
+	clip: rect(1px, 1px, 1px, 1px);
+	-webkit-clip-path: inset(50%);
+	clip-path: inset(50%);
+	border: 0;
+	word-wrap: normal !important; /* many screen reader and browser combinations announce broken words as they would appear visually */
+}
+
+#bbpress-forums {
+	background: transparent;
+	clear: both;
+	margin-bottom: 20px;
+	overflow: hidden;
+	font-size: 12px;
+	line-height: 18px;
+}
+
+#bbpress-forums a,
+#bbpress-forums a:hover {
+	box-shadow: none;
+	border: none;
+	transition: none;
+}
+
+/* Original UL styling */
+#bbpress-forums ul,
+/* Use ID & class together for increased target strength */
+#bbpress-forums.bbpress-wrapper ul {
+	background: none;
+	list-style: none;
+	margin: 0;
+	padding: 0;
+}
+
+#bbpress-forums hr {
+	margin: 0 0 24px;
+}
+
+#bbpress-forums div.even,
+#bbpress-forums ul.even {
+	background-color: #fff;
+}
+
+#bbpress-forums div.odd,
+#bbpress-forums ul.odd {
+	background-color: #fbfbfb;
+}
+
+body.reply-edit .reply {
+	float: none;
+}
+
+#bbpress-forums div.reply {
+	height: auto;
+	width: 100%;
+	box-sizing: border-box;
+}
+
+#bbpress-forums div.bbp-forum-header,
+#bbpress-forums div.bbp-topic-header,
+#bbpress-forums div.bbp-reply-header {
+	background-color: #f4f4f4;
+}
+
+#bbpress-forums .status-trash.even,
+#bbpress-forums .status-spam.even {
+	background-color: #fee;
+}
+
+#bbpress-forums .status-trash.odd,
+#bbpress-forums .status-spam.odd {
+	background-color: #fdd;
+}
+
+#bbpress-forums .status-pending.even,
+#bbpress-forums .status-pending.odd,
+#bbpress-forums ul.status-pending a {
+	background-color: #fef7f1;
+}
+
+#bbpress-forums ul.status-closed,
+#bbpress-forums ul.status-closed a {
+	color: #ccc;
+}
+
+#bbpress-forums ul.bbp-threaded-replies {
+	margin-left: 50px;
+}
+
+#bbpress-forums li {
+	background: none;
+	margin: 0;
+	list-style: none;
+}
+
+#bbpress-forums ul.bbp-lead-topic,
+#bbpress-forums ul.bbp-topics,
+#bbpress-forums ul.bbp-forums,
+#bbpress-forums ul.bbp-replies,
+#bbpress-forums ul.bbp-search-results {
+	font-size: 12px;
+	overflow: hidden;
+	border: 1px solid #eee;
+	margin-bottom: 20px;
+	clear: both;
+}
+
+#bbpress-forums li.bbp-header,
+#bbpress-forums li.bbp-body,
+#bbpress-forums li.bbp-footer {
+	clear: both;
+}
+
+#bbpress-forums li.bbp-header,
+#bbpress-forums li.bbp-footer {
+	background: #f3f3f3;
+	border-top: 1px solid #eee;
+	font-weight: 700;
+	padding: 8px;
+	text-align: center;
+}
+
+#bbpress-forums li.bbp-header {
+	background: #eaeaea;
+}
+
+#bbpress-forums li.bbp-header ul {
+	overflow: hidden;
+}
+
+#bbpress-forums .bbp-forums-list {
+	margin: 0 0 0 7px;
+	padding-left: 12px;
+	border-left: 1px solid #ddd;
+}
+
+#bbpress-forums .bbp-forums-list .bbp-forum {
+	display: inline;
+	font-size: 11px;
+}
+
+#bbpress-forums .bbp-forums-list .bbp-forum.css-sep:not(:last-child)::after {
+	content: ", ";
+}
+
+#bbpress-forums li.bbp-footer p {
+	margin: 0;
+	line-height: 1;
+}
+
+li.bbp-forum-info,
+li.bbp-topic-title {
+	float: left;
+	text-align: left;
+	width: 55%;
+}
+
+li.bbp-forum-topic-count,
+li.bbp-topic-voice-count,
+li.bbp-forum-reply-count,
+li.bbp-topic-reply-count {
+	float: left;
+	text-align: center;
+	width: 10%;
+}
+
+li.bbp-forum-freshness,
+li.bbp-topic-freshness {
+	text-align: center;
+	float: left;
+	width: 22%;
+}
+
+#bbpress-forums li.bbp-body ul.forum,
+#bbpress-forums li.bbp-body ul.topic {
+	border-top: 1px solid #eee;
+	overflow: hidden;
+	padding: 8px;
+}
+
+#bbpress-forums #subscription-toggle,
+#bbpress-forums #favorite-toggle {
+	float: right;
+}
+
+#bbpress-forums div.bbp-forum-title h3,
+#bbpress-forums div.bbp-topic-title h3,
+#bbpress-forums div.bbp-reply-title h3 {
+	background: none;
+	border: none;
+	font-size: 16px;
+	line-height: 1;
+	margin: 8px 0;
+	padding: 0;
+	text-transform: none;
+}
+
+#bbpress-forums div.bbp-forum-author,
+#bbpress-forums div.bbp-topic-author,
+#bbpress-forums div.bbp-reply-author {
+	float: left;
+	text-align: center;
+	width: 115px;
+}
+
+#bbpress-forums div.bbp-forum-author img.avatar,
+#bbpress-forums div.bbp-topic-author img.avatar,
+#bbpress-forums div.bbp-reply-author img.avatar {
+	border: none;
+	max-width: 80px;
+	padding: 0;
+	margin: 12px auto 0;
+	float: none;
+}
+
+#bbpress-forums div.bbp-forum-author .bbp-author-name,
+#bbpress-forums div.bbp-topic-author .bbp-author-name,
+#bbpress-forums div.bbp-reply-author .bbp-author-name {
+	margin: 0 12px;
+	word-wrap: break-word;
+	display: inline-block;
+}
+
+#bbpress-forums div.bbp-topic-author .bbp-author-name,
+#bbpress-forums div.bbp-reply-author .bbp-author-name {
+	clear: left;
+	display: block;
+}
+
+#bbpress-forums div.bbp-forum-author .bbp-author-role,
+#bbpress-forums div.bbp-topic-author .bbp-author-role,
+#bbpress-forums div.bbp-reply-author .bbp-author-role {
+	font-size: 11px;
+	font-style: italic;
+}
+
+#bbpress-forums li.bbp-header .bbp-search-author,
+#bbpress-forums li.bbp-footer .bbp-search-author,
+#bbpress-forums li.bbp-header .bbp-forum-author,
+#bbpress-forums li.bbp-footer .bbp-forum-author,
+#bbpress-forums li.bbp-header .bbp-topic-author,
+#bbpress-forums li.bbp-footer .bbp-topic-author,
+#bbpress-forums li.bbp-header .bbp-reply-author,
+#bbpress-forums li.bbp-footer .bbp-reply-author {
+	float: left;
+	margin: 0;
+	padding: 0;
+	width: 120px;
+}
+
+#bbpress-forums li.bbp-header .bbp-search-content,
+#bbpress-forums li.bbp-footer .bbp-search-content,
+#bbpress-forums li.bbp-header .bbp-forum-content,
+#bbpress-forums li.bbp-footer .bbp-forum-content,
+#bbpress-forums li.bbp-header .bbp-topic-content,
+#bbpress-forums li.bbp-footer .bbp-topic-content,
+#bbpress-forums li.bbp-header .bbp-reply-content,
+#bbpress-forums li.bbp-footer .bbp-reply-content {
+	margin-left: 140px;
+	padding: 0;
+	text-align: left;
+}
+
+div.bbp-forum-header,
+div.bbp-topic-header,
+div.bbp-reply-header,
+li.bbp-body div.hentry {
+	margin: 0;
+	overflow: hidden;
+	padding: 8px;
+}
+
+div.bbp-forum-header,
+div.bbp-topic-header,
+div.bbp-reply-header {
+	border-top: 1px solid #ddd;
+	clear: both;
+}
+
+span.bbp-author-ip {
+	font-size: 11px;
+	font-weight: 700;
+	word-wrap: break-word;
+	color: #747474;
+}
+
+/* =Topic and reply content
+-------------------------------------------------------------- */
+
+#bbpress-forums div.bbp-forum-content,
+#bbpress-forums div.bbp-topic-content,
+#bbpress-forums div.bbp-reply-content {
+	margin-left: 130px;
+	padding: 12px 12px 12px 0;
+	text-align: left;
+	position: relative;
+}
+
+/* Clearing hack */
+#bbpress-forums div.bbp-forum-content::after,
+#bbpress-forums div.bbp-topic-content::after,
+#bbpress-forums div.bbp-reply-content::after {
+	clear: both;
+	content: ".";
+	display: block;
+	float: none;
+	height: 0;
+	font-size: 0;
+	visibility: hidden;
+}
+
+#bbpress-forums div.bbp-topic-content a,
+#bbpress-forums div.bbp-reply-content a {
+	background: none;
+	border: none;
+	display: inline;
+	font-weight: 400;
+	margin: 0;
+	padding: 0;
+	text-decoration: underline;
+}
+
+#bbpress-forums div.bbp-topic-content a.bbp-user-mention,
+#bbpress-forums div.bbp-reply-content a.bbp-user-mention {
+	background: none;
+	border: none;
+	text-decoration: none;
+	text-shadow: none;
+	display: inline;
+	font-weight: 600;
+	margin: 0;
+	padding: 0;
+}
+
+#bbpress-forums div.bbp-topic-content h1,
+#bbpress-forums div.bbp-reply-content h1,
+#bbpress-forums div.bbp-topic-content h2,
+#bbpress-forums div.bbp-reply-content h2,
+#bbpress-forums div.bbp-topic-content h3,
+#bbpress-forums div.bbp-reply-content h3,
+#bbpress-forums div.bbp-topic-content h4,
+#bbpress-forums div.bbp-reply-content h4,
+#bbpress-forums div.bbp-topic-content h5,
+#bbpress-forums div.bbp-reply-content h5,
+#bbpress-forums div.bbp-topic-content h6,
+#bbpress-forums div.bbp-reply-content h6 {
+	clear: none;
+	line-height: 1;
+	margin: 24px 0;
+	padding: 0;
+}
+
+#bbpress-forums div.bbp-topic-content img,
+#bbpress-forums div.bbp-reply-content img {
+	max-width: 100%;
+	height: auto;
+}
+
+#bbpress-forums div.bbp-topic-content ul,
+#bbpress-forums div.bbp-reply-content ul,
+#bbpress-forums div.bbp-topic-content ol,
+#bbpress-forums div.bbp-reply-content ol {
+	margin: 0 15px 15px;
+	padding: 0;
+}
+
+#bbpress-forums div.bbp-topic-content ul li,
+#bbpress-forums div.bbp-reply-content ul li {
+	list-style-type: disc;
+}
+
+#bbpress-forums div.bbp-topic-content ol li,
+#bbpress-forums div.bbp-reply-content ol li {
+	list-style-type: decimal;
+}
+
+#bbpress-forums div.bbp-topic-content ol li li,
+#bbpress-forums div.bbp-reply-content ol li li {
+	list-style-type: lower-alpha;
+}
+
+#bbpress-forums div.bbp-topic-content ol li li li,
+#bbpress-forums div.bbp-reply-content ol li li li {
+	list-style-type: upper-roman;
+}
+
+#bbpress-forums div.bbp-topic-content code,
+#bbpress-forums div.bbp-reply-content code,
+#bbpress-forums div.bbp-topic-content pre,
+#bbpress-forums div.bbp-reply-content pre {
+	font-family: Inconsolata, Consolas, Monaco, "Lucida Console", monospace;
+	display: inline-block;
+	background-color: #f9f9f9;
+	border: 1px solid #ddd;
+	padding: 0 5px;
+	max-width: 95%;
+	vertical-align: middle;
+	margin-top: -3px;
+	overflow-x: auto;
+}
+
+#bbpress-forums div.bbp-topic-content pre,
+#bbpress-forums div.bbp-reply-content pre {
+	display: block;
+	line-height: 18px;
+	margin: 0 0 24px;
+	padding: 5px 10px;
+	white-space: pre;
+	overflow: auto;
+}
+
+#bbpress-forums div.bbp-topic-content pre code,
+#bbpress-forums div.bbp-reply-content pre code {
+	display: block;
+	border: none;
+	padding: 0;
+	margin: 0;
+	background-color: transparent;
+	overflow-wrap: normal;
+	overflow: auto;
+	max-width: 100%;
+}
+
+#bbpress-forums div.bbp-topic-content iframe.wp-embedded-content,
+#bbpress-forums div.bbp-reply-content iframe.wp-embedded-content {
+	position: relative;
+}
+
+/* =Reply to
+-------------------------------------------------------------- */
+
+#bbpress-forums div.bbp-reply-to {
+	margin-left: 130px;
+	padding: 12px 12px 12px 0;
+	text-align: right;
+}
+
+#bbpress-forums div#bbp-cancel-reply-to {
+	text-align: right;
+}
+
+/* =Breadcrumb and Tags
+-------------------------------------------------------------- */
+
+div.bbp-breadcrumb {
+	float: left;
+}
+
+div.bbp-breadcrumb,
+div.bbp-topic-tags {
+	font-size: 12px;
+}
+
+#bbpress-forums div.bbp-breadcrumb p,
+#bbpress-forums div.bbp-topic-tags p {
+	margin: 0 0 10px 0;
+}
+
+#bbpress-forums div.bbp-topic-tags {
+	float: right;
+	clear: both;
+}
+
+#bbp-topic-hot-tags {
+	clear: both;
+}
+
+/* =Search
+-------------------------------------------------------------- */
+
+#bbpress-forums #bbp-search-form {
+	clear: left;
+}
+
+#bbpress-forums #bbp-search-form .hidden {
+	height: 0;
+	width: 0;
+	overflow: hidden;
+	position: absolute;
+	background: none;
+	left: -999em;
+}
+
+#bbpress-forums #bbp-search-form #bbp_search {
+	display: inline-block;
+	width: auto;
+}
+
+#bbpress-forums div.bbp-search-form {
+	float: right;
+}
+
+/* =Admin Links
+-------------------------------------------------------------- */
+
+span.bbp-admin-links {
+	float: right;
+	color: #ddd;
+}
+
+span.bbp-admin-links a {
+	color: #707070;
+	font-weight: 400;
+	font-size: 10px;
+	text-transform: uppercase;
+	text-decoration: none;
+}
+
+fieldset span.bbp-admin-links {
+	float: left;
+}
+
+tr td span.bbp-admin-links a:hover {
+	color: #ff4b33;
+}
+
+td.bbp-topic-admin-links,
+td.bbp-topic-counts {
+	width: 50%;
+}
+
+.bbp-forum-header a.bbp-forum-permalink,
+.bbp-topic-header a.bbp-topic-permalink,
+.bbp-reply-header a.bbp-reply-permalink {
+	float: right;
+	margin-left: 10px;
+	color: #707070;
+}
+
+/* =Toggles
+-------------------------------------------------------------- */
+
+.bbp-row-actions #favorite-toggle a {
+	text-decoration: none;
+	padding: 0 3px 1px;
+	color: #7c7;
+	border: 1px solid #aca;
+	background-color: #dfd;
+	font-weight: 700;
+	font-size: 12px;
+}
+
+.bbp-row-actions #favorite-toggle a:hover {
+	color: #5a5;
+	border-color: #7c7;
+	background-color: #beb;
+}
+
+.bbp-row-actions #favorite-toggle span.is-favorite a {
+	color: #faa;
+	border: 1px solid #faa;
+	background-color: #fee;
+}
+
+.bbp-row-actions #favorite-toggle span.is-favorite a:hover {
+	color: #c88;
+	border-color: #c88;
+	background-color: #fdd;
+}
+
+.bbp-row-actions #subscription-toggle a {
+	text-decoration: none;
+	padding: 0 3px 1px;
+	color: #7c7;
+	border: 1px solid #aca;
+	background-color: #dfd;
+	font-weight: 700;
+	font-size: 12px;
+}
+
+.bbp-row-actions #subscription-toggle a:hover {
+	color: #5a5;
+	border-color: #7c7;
+	background-color: #beb;
+}
+
+.bbp-row-actions #subscription-toggle span.is-subscribed a {
+	color: #faa;
+	border: 1px solid #faa;
+	background-color: #fee;
+}
+
+.bbp-row-actions #subscription-toggle span.is-subscribed a:hover {
+	color: #c88;
+	border-color: #c88;
+	background-color: #fdd;
+}
+
+#bbpress-forums .bbp-forum-info .bbp-forum-content,
+#bbpress-forums p.bbp-topic-meta {
+	font-size: 11px;
+	margin: 5px 0;
+	padding: 0;
+	word-wrap: break-word;
+}
+
+#bbpress-forums p.bbp-topic-meta span {
+	white-space: nowrap;
+}
+
+/* =Pagination
+-------------------------------------------------------------- */
+
+.bbp-pagination-count {
+	float: left;
+	border: 1px solid transparent;
+}
+
+.bbp-pagination-links {
+	float: right;
+	list-style: none;
+	display: inline;
+}
+
+.bbp-pagination {
+	float: left;
+	width: 100%;
+	margin-bottom: 15px;
+}
+
+#bbpress-forums .bbp-pagination-links a,
+#bbpress-forums .bbp-pagination-links span.current {
+	display: block;
+	float: left;
+	font-size: 12px;
+	line-height: 18px;
+	padding: 0 5px;
+	margin-left: 5px;
+	border: 1px solid #efefef;
+	border-radius: 0;
+	background: transparent;
+	color: inherit;
+	text-decoration: none;
+}
+
+#bbpress-forums .bbp-pagination-links span.dots {
+	display: block;
+	float: left;
+	padding: 1px 4px;
+	margin-left: 5px;
+}
+
+#bbpress-forums .bbp-topic-pagination {
+	display: inline-block;
+	margin-left: 5px;
+	margin-bottom: 2px;
+}
+
+#bbpress-forums .bbp-topic-pagination a {
+	font-size: 10px;
+	font-weight: 400;
+	line-height: 10px;
+	margin: 0;
+	padding: 1px 3px;
+	border: 1px solid #ddd;
+	color: inherit;
+	background: none;
+	display: inline;
+	text-decoration: none;
+}
+
+#bbpress-forums .bbp-topic-pagination a:hover,
+#bbpress-forums .bbp-pagination-links a:hover,
+#bbpress-forums .bbp-pagination-links span.current {
+	background: #eee;
+	opacity: 0.8;
+	border: 1px solid #ddd;
+}
+
+/* =Forms
+-------------------------------------------------------------- */
+
+#bbpress-forums fieldset.bbp-form {
+	clear: left;
+	border: 1px solid #eee;
+	padding: 10px 20px;
+	margin-bottom: 10px;
+}
+
+#bbpress-forums fieldset.bbp-form legend {
+	padding: 5px;
+	width: auto;
+	border: none;
+}
+
+#bbpress-forums fieldset.bbp-form label {
+	margin: 0;
+	display: inline-block;
+}
+
+#bbp-edit-topic-tag.bbp-form fieldset.bbp-form label,
+#bbp-login fieldset label,
+#bbp-register fieldset label,
+#bbp-lost-pass fieldset label {
+	width: 100px;
+}
+
+/* Some themes aggressively add padding to paragraph tags */
+#bbpress-forums fieldset.bbp-form p {
+	padding: 0;
+}
+
+#bbpress-forums fieldset.bbp-form p,
+#bbpress-forums fieldset.bbp-form textarea,
+#bbpress-forums fieldset.bbp-form select,
+#bbpress-forums fieldset.bbp-form input {
+	margin: 0 0 8px;
+}
+
+/* Try to prevent untall selects & text boxes */
+#bbpress-forums fieldset.bbp-form select,
+#bbpress-forums fieldset.bbp-form input[type="text"] {
+	min-height: 28px;
+	height: 28px;
+	padding: 5px;
+}
+
+textarea#bbp_reply_content,
+textarea#bbp_topic_content,
+textarea#bbp_forum_content {
+	width: 97%;
+	box-sizing: border-box;
+}
+
+textarea#bbp_forum_content {
+	height: 210px;
+}
+
+#bbpress-forums fieldset.bbp-forum-form-attributes {
+	width: 200px;
+	float: right;
+	clear: none;
+	margin-left: 25px;
+}
+
+/**
+ * Fix for too-long forum names, usually from conversion
+ */
+#bbpress-forums fieldset select#bbp_forum_id {
+	max-width: 200px;
+}
+
+.bbp-topic-form,
+.bbp-reply-form,
+.bbp-topic-tag-form {
+	clear: left;
+}
+
+body.topic-edit .bbp-topic-form div.avatar img,
+body.reply-edit .bbp-reply-form div.avatar img,
+body.single-forum .bbp-topic-form div.avatar img,
+body.single-reply .bbp-reply-form div.avatar img {
+	margin-right: 0;
+	padding: 10px;
+	border: 1px solid #ddd;
+	line-height: 0;
+	background-color: #efefef;
+}
+
+body.page .bbp-reply-form code,
+body.page .bbp-topic-form code,
+body.single-topic .bbp-reply-form code,
+body.single-forum .bbp-topic-form code,
+body.topic-edit .bbp-topic-form code,
+body.reply-edit .bbp-reply-form code {
+	font-size: 10px;
+	background-color: #f0fff8;
+	border: 1px solid #ceefe1;
+	display: block;
+	padding: 8px;
+	margin-top: 5px;
+	width: 369px;
+}
+
+#merge_tag,
+#delete_tag {
+	display: inline;
+}
+
+div.bbp-submit-wrapper {
+	margin-top: 15px;
+	float: right;
+	clear: both;
+}
+
+p.form-allowed-tags {
+	max-width: 100%;
+}
+
+/* =TinyMCE in themes
+-------------------------------------------------------------- */
+#bbpress-forums div.bbp-the-content-wrapper {
+	margin-bottom: 10px;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper textarea.bbp-the-content {
+	width: 100%;
+	margin: 0;
+	font-size: 12px;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper table,
+#bbpress-forums div.bbp-the-content-wrapper tbody,
+#bbpress-forums div.bbp-the-content-wrapper tr,
+#bbpress-forums div.bbp-the-content-wrapper td {
+	border: none;
+	padding: 0;
+	margin: 0;
+	width: auto;
+	line-height: 1;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper input[type="button"] {
+	padding: 4px 7px;
+	margin: 0 -1px 0 0;
+	line-height: 0;
+	border: 1px solid transparent;
+	background-color: transparent;
+	box-shadow: none;
+	text-shadow: none;
+	text-transform: uppercase;
+	font-size: 11px;
+	font-weight: 400;
+	border-radius: 3px;
+	min-width: 25px;
+	color: #555;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper input[type="button"]:hover {
+	border-color: rgba(0, 0, 0, 0.1);
+	background-color: rgba(255, 255, 255, 0.5);
+}
+
+#bbpress-forums div.bbp-the-content-wrapper div.quicktags-toolbar {
+	padding: 5px;
+	min-height: 26px;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper td.mceToolbar {
+	padding: 4px 4px 8px;
+}
+
+#bbpress-forums div.wp-editor-container {
+	margin: 0;
+	padding: 0;
+	line-height: 0;
+}
+
+#bbpress-forums div.bbp-the-content-wrapper td.mceStatusbar {
+	line-height: 16px;
+}
+
+#bbpress-forums .bbp-replies .bbp-reply-form {
+	margin: 0 10px 0 10px;
+}
+
+/* =Edit User
+-------------------------------------------------------------- */
+
+#bbpress-forums #bbp-your-profile fieldset {
+	padding: 20px 20px 0;
+}
+
+#bbpress-forums #bbp-your-profile fieldset div {
+	margin-bottom: 20px;
+	float: left;
+	width: 100%;
+	clear: left;
+}
+
+#bbpress-forums #bbp-your-profile fieldset select {
+	margin-bottom: 0;
+	width: 60%;
+	background-color: #f9f9f9;
+	border: 1px solid #ddd;
+}
+
+#bbpress-forums #bbp-your-profile fieldset input,
+#bbpress-forums #bbp-your-profile fieldset textarea {
+	margin-bottom: 0;
+	width: 60%;
+	background-color: #f9f9f9;
+	border: 1px solid #ddd;
+	box-shadow: none;
+	border-radius: 0;
+}
+
+#bbpress-forums #bbp-your-profile fieldset input:focus,
+#bbpress-forums #bbp-your-profile fieldset textarea:focus {
+	border: 1px solid #ccc;
+	box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.1);
+	outline-color: rgba(240, 255, 240, 0.1);
+}
+
+#bbpress-forums #bbp-your-profile fieldset.bbp-form input.checkbox {
+	width: auto;
+}
+
+#bbpress-forums #bbp-your-profile fieldset legend {
+	display: none;
+}
+
+#bbpress-forums #bbp-your-profile fieldset label[for] {
+	float: left;
+	width: 20%;
+	padding: 5px 20px 5px 0;
+	text-align: right;
+	cursor: pointer;
+}
+
+#bbpress-forums #bbp-your-profile fieldset dl label[for] {
+	text-align: left;
+	width: 60%;
+}
+
+#bbpress-forums #bbp-your-profile fieldset p.description {
+	margin: 5px 0 0 20%;
+	font-size: 12px;
+	font-style: italic;
+	float: left;
+	clear: left;
+	width: 60%;
+	padding: 5px 8px;
+	border: #cee1ef 1px solid;
+	background-color: #f0f8ff;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset {
+	margin: 0;
+	border: none;
+	padding: 0;
+	clear: none;
+	float: none;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.capabilities dl {
+	margin: 0;
+}
+
+#bbpress-forums #bbp-your-profile fieldset.submit button {
+	float: right;
+}
+
+/* =Password Generator
+-------------------------------------------------------------- */
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password {
+	width: 60%;
+	display: inline;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password .password-input-wrapper,
+#bbpress-forums #bbp-your-profile fieldset fieldset.password .password-button-wrapper {
+	width: 100%;
+	margin: 0;
+	display: inline-block;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password .password-button-wrapper button {
+	width: 46%;
+	margin: 4% 2%;
+	box-sizing: border-box;
+	display: inline-block;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password input,
+#bbpress-forums #bbp-your-profile fieldset fieldset.passwword textarea {
+	width: 100%;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password button .dashicons {
+	display: none;
+}
+
+#bbpress-forums #bbp-your-profile fieldset fieldset.password span.description {
+	margin-left: 0;
+	margin-bottom: 20px;
+}
+
+#bbpress-forums #password #pass-strength-result {
+	background-color: #eee;
+	border: 1px solid #ddd;
+	color: #23282d;
+	margin: 0;
+	padding: 8px;
+	text-align: center;
+	box-sizing: border-box;
+	display: inline-block;
+}
+
+#bbpress-forums #password #pass-strength-result.short {
+	background-color: #f1adad;
+	border-color: #e35b5b;
+	opacity: 1;
+}
+
+#bbpress-forums #password #pass-strength-result.bad {
+	background-color: #fbc5a9;
+	border-color: #f78b53;
+	opacity: 1;
+}
+
+#bbpress-forums #password #pass-strength-result.good {
+	background-color: #ffe399;
+	border-color: #ffc733;
+	opacity: 1;
+}
+
+#bbpress-forums #password #pass-strength-result.strong {
+	background-color: #c1e1b9;
+	border-color: #83c373;
+	opacity: 1;
+}
+
+#bbpress-forums #password #pass1.short,
+#bbpress-forums #password #pass1-text.short {
+	border-color: #e35b5b;
+}
+
+#bbpress-forums #password#pass1.bad,
+#bbpress-forums #password #pass1-text.bad {
+	border-color: #f78b53;
+}
+
+#bbpress-forums #password #pass1.good,
+#bbpress-forums #password #pass1-text.good {
+	border-color: #ffc733;
+}
+
+#bbpress-forums #password #pass1.strong,
+#bbpress-forums #password #pass1-text.strong {
+	border-color: #83c373;
+}
+
+#bbpress-forums .indicator-hint {
+	padding-top: 8px;
+}
+
+#bbpress-forums .pw-weak,
+#bbpress-forums #pass1-text,
+#bbpress-forums .show-password #pass1 {
+	display: none;
+}
+
+#bbpress-forums .show-password #pass1-text {
+	display: inline-block;
+}
+
+/* =Notices
+-------------------------------------------------------------- */
+
+div.bbp-template-notice,
+div.indicator-hint {
+	border-width: 1px;
+	border-style: solid;
+	padding: 0 0.6em;
+	margin: 5px 0 15px;
+	border-radius: 3px;
+	background-color: #ffffe0;
+	border-color: #e6db55;
+	color: #000;
+	clear: both;
+}
+
+div.bbp-template-notice a {
+	color: #555;
+	text-decoration: none;
+
+}
+
+div.bbp-template-notice a:hover {
+	color: #000;
+}
+
+div.bbp-template-notice.info {
+	border: #cee1ef 1px solid;
+	background-color: #f0f8ff;
+}
+
+div.bbp-template-notice.important {
+	border: #e6db55 1px solid;
+	background-color: #fffbcc;
+}
+
+div.bbp-template-notice.error,
+div.bbp-template-notice.warning {
+	background-color: #ffebe8;
+	border-color: #c00;
+}
+
+div.bbp-template-notice.error a,
+div.bbp-template-notice.warning a {
+	color: #c00;
+}
+
+div.bbp-template-notice p,
+div.bbp-template-notice li {
+	margin: 0.5em 0 6px !important;
+	padding: 2px;
+	font-size: 12px;
+	line-height: 1.4;
+}
+
+#bbpress-forums div.bbp-template-notice code {
+	font-size: 11px;
+	padding: 2px 4px;
+	background: rgba(200, 200, 200, 0.3);
+	border-radius: 3px;
+}
+
+/* =Stickies
+-------------------------------------------------------------- */
+
+.bbp-topics-front ul.super-sticky,
+.bbp-topics ul.super-sticky,
+.bbp-topics ul.sticky,
+.bbp-forum-content ul.sticky {
+	background-color: #ffffe0 !important;
+	font-size: 1.1em;
+}
+
+/* =Revisions
+-------------------------------------------------------------- */
+
+#bbpress-forums .bbp-topic-content ul.bbp-topic-revision-log,
+#bbpress-forums .bbp-reply-content ul.bbp-topic-revision-log,
+#bbpress-forums .bbp-reply-content ul.bbp-reply-revision-log {
+	border-top: 1px dotted #ddd;
+	width: 100%;
+	margin: 0;
+	padding: 8px 0 0;
+	font-size: 11px;
+	color: #aaa;
+}
+
+#bbpress-forums .bbp-topic-content ul.bbp-topic-revision-log li,
+#bbpress-forums .bbp-reply-content ul.bbp-topic-revision-log li,
+#bbpress-forums .bbp-reply-content ul.bbp-reply-revision-log li {
+	list-style-type: none;
+}
+
+#bbpress-forums .bbp-topic-content ul.bbp-topic-revision-log a,
+#bbpress-forums .bbp-reply-content ul.bbp-topic-revision-log a,
+#bbpress-forums .bbp-reply-content ul.bbp-reply-revision-log a {
+	text-decoration: none;
+}
+
+/* =Widgets
+-------------------------------------------------------------- */
+
+.bbp-login-form fieldset legend {
+	display: none;
+}
+
+.bbp-login-form .bbp-username input,
+.bbp-login-form .bbp-email input,
+.bbp-login-form .bbp-password input {
+	padding: 5px;
+}
+
+.bbp-login-form label {
+	width: 140px;
+	display: inline-block;
+}
+
+#sidebar .bbp-login-form label {
+	width: 70px;
+}
+
+.bbp-login-form .bbp-username,
+.bbp-login-form .bbp-email,
+.bbp-login-form .bbp-password,
+.bbp-login-form .bbp-remember-me,
+.bbp-login-form .bbp-submit-wrapper {
+	margin-top: 10px;
+}
+
+.bbp-login-form .bbp-submit-wrapper {
+	text-align: right;
+}
+
+.bbp-login-form .bbp-login-links a {
+	float: left;
+	clear: left;
+}
+
+.bbp-logged-in img.avatar {
+	float: left;
+	margin: 0 15px 0 0;
+}
+
+.bbp-logged-in h4 {
+	font-weight: 700;
+	font-size: 1.3em;
+	clear: none;
+	margin-bottom: 10px;
+}
+
+/* =Avatars
+-------------------------------------------------------------- */
+
+#bbpress-forums img.avatar {
+	box-shadow: none;
+	display: inline-block;
+	margin-right: 4px;
+}
+
+#bbpress-forums p.bbp-topic-meta img.avatar,
+#bbpress-forums ul.bbp-reply-revision-log img.avatar,
+#bbpress-forums ul.bbp-topic-revision-log img.avatar,
+#bbpress-forums div.bbp-template-notice img.avatar,
+#bbpress-forums .widget_display_topics img.avatar,
+#bbpress-forums .widget_display_replies img.avatar {
+	float: none;
+	vertical-align: middle;
+	border: 3px double #ddd;
+	max-height: 20px;
+	max-width: 20px;
+}
+
+fieldset div.avatar {
+	float: right;
+}
+
+/* =BuddyPress Activity Streams
+-------------------------------------------------------------- */
+
+.activity-list li.bbp_topic_create .activity-content .activity-inner,
+.activity-list li.bbp_reply_create .activity-content .activity-inner {
+	border-left: 2px solid #eaeaea;
+	margin-left: 5px;
+	padding-left: 10px;
+}
+
+/* =User Pages
+-------------------------------------------------------------- */
+
+#bbpress-forums h1 {
+	clear: none;
+	font-size: 1.8em;
+	line-height: 1;
+	padding-bottom: 10px;
+}
+
+#bbpress-forums #bbp-user-wrapper {
+	float: left;
+	width: 100%;
+}
+
+#bbpress-forums .bbp-user-section {
+	overflow: auto;
+	clear: right;
+}
+
+#bbpress-forums #bbp-user-wrapper h2.entry-title {
+	font-size: 1.4em;
+	margin: 0;
+	padding-bottom: 10px;
+	padding-top: 0;
+	clear: none;
+}
+
+#bbpress-forums #bbp-user-wrapper ul.bbp-lead-topic,
+#bbpress-forums #bbp-user-wrapper ul.bbp-topics,
+#bbpress-forums #bbp-user-wrapper ul.bbp-forums,
+#bbpress-forums #bbp-user-wrapper ul.bbp-replies,
+#bbpress-forums #bbp-user-wrapper fieldset.bbp-form {
+	clear: none;
+}
+
+#bbpress-forums #bbp-single-user-details {
+	margin: 0;
+	width: 150px;
+	float: left;
+	overflow: hidden;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-avatar {
+	margin: 0;
+	width: 150px;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-avatar img.avatar {
+	border: none;
+	height: 150px;
+	padding: 0;
+	margin: 0 0 20px;
+	width: 150px;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-description {
+	float: none;
+	margin-left: 180px;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-navigation {
+	float: none;
+	margin: 0;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-navigation li {
+	margin: 0;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-navigation a {
+	padding: 5px 8px;
+	display: block;
+	border: 1px solid transparent;
+	text-decoration: none;
+}
+
+#bbpress-forums #bbp-single-user-details #bbp-user-navigation li.current a {
+	background: #eee;
+	opacity: 0.8;
+}
+
+#bbpress-forums #bbp-user-body {
+	margin-left: 180px;
+}
+
+body.my-account #bbpress-forums {
+	border-top: none;
+	padding-top: 0;
+	margin-bottom: 0;
+}
+
+/* =User Capabilities
+-------------------------------------------------------------- */
+
+#bbpress-forums dl.bbp-user-capabilities {
+	display: inline-block;
+	vertical-align: top;
+}
+
+#bbpress-forums dl.bbp-user-capabilities dt {
+	margin: 0 0 10px;
+	text-transform: capitalize;
+}
+
+#bbpress-forums dl.bbp-user-capabilities dd {
+	margin: 0;
+	padding: 0;
+}
+
+/* =Alerts
+-------------------------------------------------------------- */
+
+.bbp-alert-outer {
+	height: 100%;
+	width: 100%;
+	top: 0;
+	left: 0;
+	position: fixed;
+	background-color: rgba(0, 0, 0, 0.2);
+	z-index: 99999;
+}
+
+.bbp-alert-inner {
+	width: 350px;
+	text-align: center;
+	background: #fff;
+	position: fixed;
+	top: 50%;
+	left: 50%;
+	margin-top: -75px;
+	margin-left: -185px;
+	border-radius: 3px;
+	border: 1px solid #aaa;
+	padding: 15px 10px 10px;
+}
+
+.bbp-alert-outer .bbp-alert-inner p {
+	margin: 10px 0;
+}
+
+.bbp-alert-actions a {
+	padding: 5px 20px;
+	text-decoration: none;
+}
+
+/*--------------------------------------------------------------
+ Media Queries
+--------------------------------------------------------------*/
+
+/* =Standard Mobile Landscape
+-------------------------------------------------------------- */
+@media only screen and (max-width: 480px) {
+
+	#bbpress-forums div.bbp-topic-tags {
+		clear: both;
+		float: none;
+	}
+
+	div.bbp-search-form input,
+	div.bbp-search-form button {
+		font-size: 11px;
+		padding: 2px;
+	}
+
+	li.bbp-forum-info,
+	li.bbp-topic-title {
+		width: 45%;
+	}
+
+	li.bbp-forum-topic-count,
+	li.bbp-forum-reply-count,
+	li.bbp-topic-voice-count,
+	li.bbp-topic-reply-count {
+		width: 15%;
+	}
+
+	span.bbp-topic-post-date,
+	span.bbp-reply-post-date {
+		float: left;
+	}
+
+	span.bbp-admin-links {
+		float: none;
+		clear: both;
+	}
+
+	#bbpress-forums .bbp-forums-list li,
+	#bbpress-forums .bbp-forums-list span {
+		display: block;
+		font-size: 11px;
+	}
+
+	#bbpress-forums .bbp-body div.bbp-topic-author,
+	#bbpress-forums .bbp-body div.bbp-reply-author {
+		float: none;
+		margin: 10px;
+		min-height: 80px;
+		padding-left: 80px;
+		position: relative;
+		text-align: left;
+		width: calc(100% - 100px);
+		border-bottom: 1px solid #efefef;
+		box-sizing: content-box;
+	}
+
+	#bbpress-forums div.bbp-topic-author .bbp-author-name,
+	#bbpress-forums div.bbp-reply-author .bbp-author-name {
+		clear: none;
+		display: inline-block;
+		margin-left: 0;
+		word-wrap: break-word;
+	}
+
+	#bbpress-forums div.bbp-topic-author img.avatar,
+	#bbpress-forums div.bbp-reply-author img.avatar {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 60px;
+		height: auto;
+		margin-top: 0;
+	}
+
+	#bbpress-forums div.bbp-topic-author .bbp-author-role,
+	#bbpress-forums div.bbp-reply-author .bbp-author-role {
+		font-size: 12px;
+		font-style: normal;
+	}
+
+	#bbpress-forums .bbp-body div.bbp-topic-content,
+	#bbpress-forums .bbp-body div.bbp-reply-content {
+		clear: both;
+		margin: 0 10px 5px 10px;
+		padding: 0;
+	}
+
+	#bbpress-forums div.bbp-topic-content p,
+	#bbpress-forums div.bbp-reply-content p {
+		margin-bottom: 1em;
+	}
+
+	#bbpress-forums div.bbp-topic-content p:last-child,
+	#bbpress-forums div.bbp-reply-content p:last-child {
+		margin-bottom: 0;
+	}
+
+	div.bbp-submit-wrapper {
+		float: left;
+	}
+
+	#bbpress-forums fieldset.bbp-form {
+		padding: 0 10px 10px;
+	}
+
+	#bbpress-forums #bbp-user-body {
+		clear: both;
+		margin-left: 0;
+		word-wrap: break-word;
+	}
+
+	#bbpress-forums div.bbp-forum-title h3,
+	#bbpress-forums div.bbp-topic-title h3,
+	#bbpress-forums div.bbp-reply-title h3 {
+		font-size: 14px;
+	}
+}
+
+/* =Standard Mobile Portrait
+-------------------------------------------------------------- */
+@media only screen and (max-width: 320px) {
+
+	#bbpress-forums div.bbp-search-form {
+		margin-bottom: 10px;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-info,
+	#bbpress-forums li.bbp-body li.bbp-forum-info,
+	#bbpress-forums li.bbp-header li.bbp-topic-title,
+	#bbpress-forums li.bbp-body li.bbp-topic-title {
+		width: 100%;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-info,
+	#bbpress-forums li.bbp-header li.bbp-topic-title {
+		text-align: center;
+		text-transform: uppercase;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-topic-count,
+	#bbpress-forums li.bbp-header li.bbp-forum-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-topic-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-reply-count,
+	#bbpress-forums li.bbp-header li.bbp-topic-voice-count,
+	#bbpress-forums li.bbp-header li.bbp-topic-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-voice-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-reply-count {
+		width: 20%;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-body li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-header li.bbp-topic-freshness,
+	#bbpress-forums li.bbp-body li.bbp-topic-freshness {
+		width: 58%;
+	}
+
+	#bbpress-forums li.bbp-body li.bbp-forum-topic-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-body li.bbp-topic-voice-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-freshness {
+		margin-top: 7px;
+	}
+
+	#bbpress-forums li.bbp-header,
+	#bbpress-forums li.bbp-footer {
+		font-size: 10px;
+	}
+
+	#bbpress-forums li.bbp-header div.bbp-search-author,
+	#bbpress-forums li.bbp-header div.bbp-topic-author,
+	#bbpress-forums li.bbp-header div.bbp-reply-author {
+		text-align: left;
+		width: 25%;
+	}
+
+	#bbpress-forums li.bbp-header div.bbp-search-content,
+	#bbpress-forums li.bbp-header div.bbp-topic-content,
+	#bbpress-forums li.bbp-header div.bbp-reply-content {
+		margin-left: 25%;
+	}
+
+	#bbpress-forums div.bbp-template-notice img.avatar,
+	#bbpress-forums p.bbp-topic-meta img.avatar {
+		width: 14px;
+		height: auto;
+		max-height: 14px;
+		max-width: 14px;
+	}
+
+	#bbpress-forums div.bbp-the-content-wrapper td.mceToolbar {
+		padding: 1px;
+	}
+
+	#bbpress-forums div.bbp-the-content-wrapper td.mceToolbar td {
+		width: 20px;
+		height: 20px;
+	}
+
+	#bbpress-forums div.wp-editor-container {
+		width: 100%;
+		overflow: auto;
+	}
+
+	#bbpress-forums input#bbp_topic_title,
+	#bbpress-forums input#bbp_topic_tags {
+		width: 95%;
+	}
+
+	#bbpress-forums div.bbp-forum-title h3,
+	#bbpress-forums div.bbp-topic-title h3,
+	#bbpress-forums div.bbp-reply-title h3 {
+		font-size: 14px;
+	}
+}
+
+/* =Extra Small Mobile
+-------------------------------------------------------------- */
+@media only screen and (max-width: 240px) {
+
+	#bbpress-forums li.bbp-header li.bbp-forum-topic-count,
+	#bbpress-forums li.bbp-header li.bbp-forum-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-topic-count,
+	#bbpress-forums li.bbp-body li.bbp-forum-reply-count,
+	#bbpress-forums li.bbp-header li.bbp-topic-voice-count,
+	#bbpress-forums li.bbp-header li.bbp-topic-reply-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-voice-count,
+	#bbpress-forums li.bbp-body li.bbp-topic-reply-count,
+	#bbpress-forums li.bbp-footer div.bbp-search-author,
+	#bbpress-forums li.bbp-footer div.bbp-topic-author,
+	#bbpress-forums li.bbp-footer div.bbp-reply-author {
+		width: 45%;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-body li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-header li.bbp-topic-freshness,
+	#bbpress-forums li.bbp-body li.bbp-topic-freshness {
+		clear: both;
+		width: 100%;
+	}
+
+	#bbpress-forums li.bbp-header li.bbp-forum-freshness,
+	#bbpress-forums li.bbp-header li.bbp-topic-freshness {
+		text-align: center;
+	}
+
+	#bbpress-forums li.bbp-body li.bbp-topic-freshness,
+	#bbpress-forums li.bbp-header div.bbp-search-author,
+	#bbpress-forums li.bbp-header div.bbp-search-content,
+	#bbpress-forums li.bbp-header div.bbp-topic-author,
+	#bbpress-forums li.bbp-header div.bbp-topic-content,
+	#bbpress-forums li.bbp-header div.bbp-reply-author,
+	#bbpress-forums li.bbp-header div.bbp-reply-content {
+		margin-left: 0;
+		text-align: left;
+	}
+
+	#bbpress-forums li.bbp-body li.bbp-topic-freshness p.bbp-topic-meta {
+		display: inline-block;
+	}
+
+	#bbpress-forums li.bbp-header {
+		overflow: hidden;
+	}
+
+	#bbpress-forums li.bbp-footer div.bbp-search-content,
+	#bbpress-forums li.bbp-footer div.bbp-topic-content,
+	#bbpress-forums li.bbp-footer div.bbp-reply-content {
+		display: inline-block;
+		margin-left: 0;
+	}
+
+	#bbpress-forums li.bbp-body div.bbp-topic-author,
+	#bbpress-forums li.bbp-body div.bbp-reply-author {
+		min-height: 60px;
+		padding-left: 60px;
+	}
+
+	#bbpress-forums div.bbp-topic-author img.avatar,
+	#bbpress-forums div.bbp-reply-author img.avatar {
+		width: 40px;
+		height: auto;
+	}
+
+	#bbpress-forums div.bbp-forum-title h3,
+	#bbpress-forums div.bbp-topic-title h3,
+	#bbpress-forums div.bbp-reply-title h3 {
+		font-size: 12px;
+	}
+}
Index: /tags/2.6.6/src/templates/default/extras/archive-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/archive-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/archive-forum.php	(revision 7162)
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * bbPress - Forum Archive
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<div id="forum-front" class="bbp-forum-front">
+		<h1 class="entry-title"><?php bbp_forum_archive_title(); ?></h1>
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'archive-forum' ); ?>
+
+		</div>
+	</div><!-- #forum-front -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/archive-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/archive-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/archive-topic.php	(revision 7162)
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * bbPress - Topic Archive
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<div id="topic-front" class="bbp-topics-front">
+		<h1 class="entry-title"><?php bbp_topic_archive_title(); ?></h1>
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'archive-topic' ); ?>
+
+		</div>
+	</div><!-- #topics-front -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-create-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-create-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-create-topic.php	(revision 7162)
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Template Name: bbPress - Create Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-new-topic" class="bbp-new-topic">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<?php bbp_get_template_part( 'form', 'topic' ); ?>
+
+			</div>
+		</div><!-- #bbp-new-topic -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-forum-statistics.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-forum-statistics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-forum-statistics.php	(revision 7162)
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Template Name: bbPress - Statistics
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-statistics" class="bbp-statistics">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php get_the_content() ? the_content() : wpautop( esc_html__( 'Here are the statistics and popular topics of our forums.', 'bbpress' ) ); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_get_template_part( 'content', 'statistics' ); ?>
+
+					<?php do_action( 'bbp_before_popular_topics' ); ?>
+
+					<?php bbp_set_query_name( 'bbp_popular_topics' ); ?>
+
+					<?php if ( bbp_view_query( 'popular' ) ) : ?>
+
+						<h2 class="entry-title"><?php esc_html_e( 'Popular Topics', 'bbpress' ); ?></h2>
+
+						<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+						<?php bbp_get_template_part( 'loop',       'topics' ); ?>
+
+						<?php bbp_get_template_part( 'pagination', 'topics' ); ?>
+
+					<?php endif; ?>
+
+					<?php bbp_reset_query_name(); ?>
+
+					<?php do_action( 'bbp_after_popular_topics' ); ?>
+
+				</div>
+			</div>
+		</div><!-- #bbp-statistics -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-front-forums.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-front-forums.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-front-forums.php	(revision 7162)
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Template Name: bbPress - Forums (Index)
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="forum-front" class="bbp-forum-front">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<?php bbp_get_template_part( 'content', 'archive-forum' ); ?>
+
+			</div>
+		</div><!-- #forum-front -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-front-topics.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-front-topics.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-front-topics.php	(revision 7162)
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Template Name: bbPress - Topics (Newest)
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="topics-front" class="bbp-topics-front">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<?php bbp_get_template_part( 'content', 'archive-topic' ); ?>
+
+			</div>
+		</div><!-- #topics-front -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-topic-tags.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-topic-tags.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-topic-tags.php	(revision 7162)
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Template Name: bbPress - Topic Tags
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-topic-tags" class="bbp-topic-tags">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php get_the_content() ? the_content() : wpautop( esc_html__( 'This is a collection of tags that are currently popular on our forums.', 'bbpress' ) ); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_breadcrumb(); ?>
+
+					<div id="bbp-topic-hot-tags">
+
+						<?php wp_tag_cloud( array( 'smallest' => 9, 'largest' => 38, 'number' => 80, 'taxonomy' => bbp_get_topic_tag_tax_id() ) ); ?>
+
+					</div>
+				</div>
+			</div>
+		</div><!-- #bbp-topic-tags -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-topics-no-replies.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-topics-no-replies.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-topics-no-replies.php	(revision 7162)
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Template Name: bbPress - Topics (No Replies)
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="topics-front" class="bbp-topics-front">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_breadcrumb(); ?>
+
+					<?php bbp_set_query_name( 'bbp_no_replies' ); ?>
+
+					<?php if ( bbp_has_topics( array( 'meta_key' => '_bbp_reply_count', 'meta_value' => '1', 'meta_type' => 'NUMERIC', 'meta_compare' => '<', 'orderby' => 'date', 'show_stickies' => false ) ) ) : ?>
+
+						<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+						<?php bbp_get_template_part( 'loop',       'topics'    ); ?>
+
+						<?php bbp_get_template_part( 'pagination', 'topics'    ); ?>
+
+					<?php else : ?>
+
+						<?php bbp_get_template_part( 'feedback',   'no-topics' ); ?>
+
+					<?php endif; ?>
+
+					<?php bbp_reset_query_name(); ?>
+
+				</div>
+			</div>
+		</div><!-- #topics-front -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-user-login.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-user-login.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-user-login.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Template Name: bbPress - User Login
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// No logged in users
+bbp_logged_in_redirect();
+
+// Begin Template
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-login" class="bbp-login">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_breadcrumb(); ?>
+
+					<?php bbp_get_template_part( 'form', 'user-login' ); ?>
+
+				</div>
+			</div>
+		</div><!-- #bbp-login -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-user-lost-pass.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-user-lost-pass.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-user-lost-pass.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Template Name: bbPress - User Lost Password
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// No logged in users
+bbp_logged_in_redirect();
+
+// Begin Template
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-lost-pass" class="bbp-lost-pass">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_breadcrumb(); ?>
+
+					<?php bbp_get_template_part( 'form', 'user-lost-pass' ); ?>
+
+				</div>
+			</div>
+		</div><!-- #bbp-lost-pass -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/page-user-register.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/page-user-register.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/page-user-register.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Template Name: bbPress - User Register
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+// No logged in users
+bbp_logged_in_redirect();
+
+// Begin Template
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-register" class="bbp-register">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php the_content(); ?>
+
+				<div id="bbpress-forums" class="bbpress-wrapper">
+
+					<?php bbp_breadcrumb(); ?>
+
+					<?php bbp_get_template_part( 'form', 'user-register' ); ?>
+
+				</div>
+			</div>
+		</div><!-- #bbp-register -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-forum-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-forum-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-forum-edit.php	(revision 7162)
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Edit handler for forums
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'forum' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-forum.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-forum.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-forum.php	(revision 7162)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Single Forum
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<?php if ( bbp_user_can_view_forum() ) : ?>
+
+			<div id="forum-<?php bbp_forum_id(); ?>" class="bbp-forum-content">
+				<h1 class="entry-title"><?php bbp_forum_title(); ?></h1>
+				<div class="entry-content">
+
+					<?php bbp_get_template_part( 'content', 'single-forum' ); ?>
+
+				</div>
+			</div><!-- #forum-<?php bbp_forum_id(); ?> -->
+
+		<?php else : // Forum exists, user no access ?>
+
+			<?php bbp_get_template_part( 'feedback', 'no-access' ); ?>
+
+		<?php endif; ?>
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-reply-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-reply-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-reply-edit.php	(revision 7162)
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Edit handler for replies
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'reply' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-reply-move.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-reply-move.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-reply-move.php	(revision 7162)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Move reply page
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'reply-move' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-reply.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-reply.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-reply.php	(revision 7162)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Single Reply
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php if ( bbp_user_can_view_forum( array( 'forum_id' => bbp_get_reply_forum_id() ) ) ) : ?>
+
+		<?php while ( have_posts() ) : the_post(); ?>
+
+			<div id="bbp-reply-wrapper-<?php bbp_reply_id(); ?>" class="bbp-reply-wrapper">
+				<h1 class="entry-title"><?php bbp_reply_title(); ?></h1>
+				<div class="entry-content">
+
+					<?php bbp_get_template_part( 'content', 'single-reply' ); ?>
+
+				</div><!-- .entry-content -->
+			</div><!-- #bbp-reply-wrapper-<?php bbp_reply_id(); ?> -->
+
+		<?php endwhile; ?>
+
+	<?php elseif ( bbp_is_forum_private( bbp_get_reply_forum_id(), false ) ) : ?>
+
+		<?php bbp_get_template_part( 'feedback', 'no-access' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-topic-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-topic-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-topic-edit.php	(revision 7162)
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Edit handler for topics
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'topic' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-topic-merge.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-topic-merge.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-topic-merge.php	(revision 7162)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Merge topic page
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'topic-merge' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-topic-split.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-topic-split.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-topic-split.php	(revision 7162)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Split topic page
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php while ( have_posts() ) : the_post(); ?>
+
+		<div id="bbp-edit-page" class="bbp-edit-page">
+			<h1 class="entry-title"><?php the_title(); ?></h1>
+			<div class="entry-content">
+
+				<?php bbp_get_template_part( 'form', 'topic-split' ); ?>
+
+			</div>
+		</div><!-- #bbp-edit-page -->
+
+	<?php endwhile; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-topic.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-topic.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-topic.php	(revision 7162)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Single Topic
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<?php if ( bbp_user_can_view_forum( array( 'forum_id' => bbp_get_topic_forum_id() ) ) ) : ?>
+
+		<?php while ( have_posts() ) : the_post(); ?>
+
+			<div id="bbp-topic-wrapper-<?php bbp_topic_id(); ?>" class="bbp-topic-wrapper">
+				<h1 class="entry-title"><?php bbp_topic_title(); ?></h1>
+				<div class="entry-content">
+
+					<?php bbp_get_template_part( 'content', 'single-topic' ); ?>
+
+				</div>
+			</div><!-- #bbp-topic-wrapper-<?php bbp_topic_id(); ?> -->
+
+		<?php endwhile; ?>
+
+	<?php elseif ( bbp_is_forum_private( bbp_get_topic_forum_id(), false ) ) : ?>
+
+		<?php bbp_get_template_part( 'feedback', 'no-access' ); ?>
+
+	<?php endif; ?>
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-user-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-user-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-user-edit.php	(revision 7162)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * bbPress User Profile Edit
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<div id="bbp-user-<?php bbp_current_user_id(); ?>" class="bbp-single-user">
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'single-user' ); ?>
+
+		</div><!-- .entry-content -->
+	</div><!-- #bbp-user-<?php bbp_current_user_id(); ?> -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-user.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-user.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-user.php	(revision 7162)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Single User
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<div id="bbp-user-<?php bbp_current_user_id(); ?>" class="bbp-single-user">
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'single-user' ); ?>
+
+		</div><!-- .entry-content -->
+	</div><!-- #bbp-user-<?php bbp_current_user_id(); ?> -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/single-view.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/single-view.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/single-view.php	(revision 7162)
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Single View
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<div id="bbp-view-<?php bbp_view_id(); ?>" class="bbp-view">
+		<h1 class="entry-title"><?php bbp_view_title(); ?></h1>
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'single-view' ); ?>
+
+		</div>
+	</div><!-- #bbp-view-<?php bbp_view_id(); ?> -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag-edit.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag-edit.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag-edit.php	(revision 7162)
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Topic Tag Edit
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<div id="topic-tag" class="bbp-topic-tag">
+		<h1 class="entry-title"><?php printf( esc_html__( 'Topic Tag: %s', 'bbpress' ), '<span>' . bbp_get_topic_tag_name() . '</span>' ); ?></h1>
+
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'topic-tag-edit' ); ?>
+
+		</div>
+	</div><!-- #topic-tag -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag.php
===================================================================
--- /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag.php	(revision 7162)
+++ /tags/2.6.6/src/templates/default/extras/taxonomy-topic-tag.php	(revision 7162)
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Topic Tag
+ *
+ * @package bbPress
+ * @subpackage Theme
+ */
+
+get_header(); ?>
+
+	<?php do_action( 'bbp_before_main_content' ); ?>
+
+	<?php do_action( 'bbp_template_notices' ); ?>
+
+	<div id="topic-tag" class="bbp-topic-tag">
+		<h1 class="entry-title"><?php printf( esc_html__( 'Topic Tag: %s', 'bbpress' ), '<span>' . bbp_get_topic_tag_name() . '</span>' ); ?></h1>
+		<div class="entry-content">
+
+			<?php bbp_get_template_part( 'content', 'archive-topic' ); ?>
+
+		</div>
+	</div><!-- #topic-tag -->
+
+	<?php do_action( 'bbp_after_main_content' ); ?>
+
+<?php get_sidebar(); ?>
+<?php get_footer();
Index: /tags/2.6.6/src/templates/default/js/editor.js
===================================================================
--- /tags/2.6.6/src/templates/default/js/editor.js	(revision 7162)
+++ /tags/2.6.6/src/templates/default/js/editor.js	(revision 7162)
@@ -0,0 +1,53 @@
+/* global edButtons, QTags, tinymce */
+jQuery(document).ready( function() {
+
+	/* Use backticks instead of <code> for the Code button in the editor */
+	if ( typeof( edButtons ) !== 'undefined' ) {
+		edButtons[110] = new QTags.TagButton( 'code', 'code', '`', '`', 'c' );
+		QTags._buttonsInit();
+	}
+
+	/* Tab from topic title */
+	jQuery( '#bbp_topic_title' ).bind( 'keydown.editor-focus', function(e) {
+		if ( e.which !== 9 ) {
+			return;
+		}
+
+		if ( !e.ctrlKey && !e.altKey && !e.shiftKey ) {
+			if ( typeof( tinymce ) !== 'undefined' ) {
+				if ( ! tinymce.activeEditor.isHidden() ) {
+					var editor = tinymce.activeEditor.editorContainer;
+					jQuery( '#' + editor + ' td.mceToolbar > a' ).focus();
+				} else {
+					jQuery( 'textarea.bbp-the-content' ).focus();
+				}
+			} else {
+				jQuery( 'textarea.bbp-the-content' ).focus();
+			}
+
+			e.preventDefault();
+		}
+	});
+
+	/* Shift + tab from topic tags */
+	jQuery( '#bbp_topic_tags' ).bind( 'keydown.editor-focus', function(e) {
+		if ( e.which !== 9 ) {
+			return;
+		}
+
+		if ( e.shiftKey && !e.ctrlKey && !e.altKey ) {
+			if ( typeof( tinymce ) !== 'undefined' ) {
+				if ( ! tinymce.activeEditor.isHidden() ) {
+					var editor = tinymce.activeEditor.editorContainer;
+					jQuery( '#' + editor + ' td.mceToolbar > a' ).focus();
+				} else {
+					jQuery( 'textarea.bbp-the-content' ).focus();
+				}
+			} else {
+				jQuery( 'textarea.bbp-the-content' ).focus();
+			}
+
+			e.preventDefault();
+		}
+	});
+});
Index: /tags/2.6.6/src/templates/default/js/engagements.js
===================================================================
--- /tags/2.6.6/src/templates/default/js/engagements.js	(revision 7162)
+++ /tags/2.6.6/src/templates/default/js/engagements.js	(revision 7162)
@@ -0,0 +1,62 @@
+/* global bbpEngagementJS */
+jQuery( document ).ready( function ( $ ) {
+
+	function bbp_ajax_call( action, object, type, nonce, update_selector ) {
+		var $data = {
+			action : action,
+			id     : object,
+			type   : type,
+			nonce  : nonce
+		};
+
+		$.post( bbpEngagementJS.bbp_ajaxurl, $data, function ( response ) {
+			if ( response.success ) {
+				$( update_selector ).html( response.content );
+			} else {
+				if ( !response.content ) {
+					response.content = bbpEngagementJS.generic_ajax_error;
+				}
+				window.alert( response.content );
+			}
+		} );
+	}
+
+	$( '#favorite-toggle' ).on( 'click', 'span a.favorite-toggle', function( e ) {
+		e.preventDefault();
+		bbp_ajax_call(
+			'favorite',
+			$( this ).data( 'bbp-object-id'   ),
+			$( this ).data( 'bbp-object-type' ),
+			$( this ).data( 'bbp-nonce'       ),
+			'#favorite-toggle'
+		);
+	} );
+
+	$( '#subscription-toggle' ).on( 'click', 'span a.subscription-toggle', function( e ) {
+		e.preventDefault();
+		bbp_ajax_call(
+			'subscription',
+			$( this ).data( 'bbp-object-id'   ),
+			$( this ).data( 'bbp-object-type' ),
+			$( this ).data( 'bbp-nonce'       ),
+			'#subscription-toggle'
+		);
+	} );
+
+	$( '.bbp-alert-outer' ).on( 'click', '.bbp-alert-close', function( e ) {
+		e.preventDefault();
+		$( this ).closest( '.bbp-alert-outer' ).fadeOut();
+	} );
+
+	$( '.bbp-alert-outer' ).on( 'click', function( e ) {
+		if ( this === e.target ) {
+			$( this ).closest( '.bbp-alert-outer' ).fadeOut();
+		}
+	} );
+
+	$( document ).keyup( function( e ) {
+		if ( e.keyCode === 27 ) {
+			$( '.bbp-alert-outer .bbp-alert-close' ).click();
+		}
+	} );
+} );
Index: /tags/2.6.6/src/templates/default/js/reply.js
===================================================================
--- /tags/2.6.6/src/templates/default/js/reply.js	(revision 7162)
+++ /tags/2.6.6/src/templates/default/js/reply.js	(revision 7162)
@@ -0,0 +1,239 @@
+/* globals tinyMCE */
+addReply = {
+
+	/**
+	 * Move the reply form when "Reply" is clicked.
+	 *
+	 * @since 2.6.2
+	 * @param {string} replyId
+	 * @param {string} parentId
+	 * @param {string} respondId
+	 * @param {string} postId
+	 * @returns {undefined|Boolean}
+	 */
+	moveForm: function ( replyId, parentId, respondId, postId ) {
+
+		/* Get initial elements */
+		var t       = this,
+			reply   = t.getElement( replyId ),
+			respond = t.getElement( respondId ),
+			cancel  = t.getElement( 'bbp-cancel-reply-to-link' ),
+			parent  = t.getElement( 'bbp_reply_to' ),
+			post    = t.getElement( 'bbp_topic_id' );
+
+		/* Remove the editor, if its already been moved */
+		t.removeEditor();
+
+		/* Allow click to go through */
+		if ( ! reply || ! respond || ! cancel || ! parent ) {
+			return;
+		}
+
+		t.respondId = respondId;
+		postId      = postId || false;
+
+		/* Setup a temporary div for relocating back when clicking cancel */
+		if ( ! t.getElement( 'bbp-temp-form-div' ) ) {
+			var div = document.createElement( 'div' );
+
+			div.id            = 'bbp-temp-form-div';
+			div.style.display = 'none';
+
+			respond.parentNode.appendChild( div );
+		}
+
+		/* Relocate the element */
+		reply.parentNode.appendChild( respond );
+
+		if ( post && postId ) {
+			post.value = postId;
+		}
+
+		parent.value         = parentId;
+		cancel.style.display = '';
+
+		/* Add the editor where it now belongs */
+		t.addEditor();
+
+		/**
+		 * When cancelling a Reply.
+		 *
+		 * @since 2.6.2
+		 * @returns {void}
+		 */
+		cancel.onclick = function () {
+			t.cancelForm( this );
+		};
+
+		t.scrollToForm();
+
+		/* Prevent click from going through */
+		return false;
+	},
+
+	/**
+	 * Cancel the reply form.
+	 *
+	 * @since 2.6.6
+	 * @returns {void}
+	 */
+	cancelForm: function () {
+		var r       = addReply,
+			temp    = r.getElement( 'bbp-temp-form-div' ),
+			cancel  = r.getElement( 'bbp-cancel-reply-to-link' ),
+			respond = r.getElement( r.respondId );
+
+		r.removeEditor();
+
+		/* Allow click to go through */
+		if ( ! temp || ! respond ) {
+			return;
+		}
+
+		r.getElement( 'bbp_reply_to' ).value = '0';
+
+		temp.parentNode.insertBefore( respond, temp );
+		temp.parentNode.removeChild( temp );
+
+		cancel.style.display = 'none';
+		cancel.onclick       = null;
+
+		r.addEditor();
+		r.scrollToForm();
+
+		/* Prevent click from going through */
+		return false;
+	},
+
+	/**
+	 * Scrolls to the top of the page.
+	 *
+	 * @since 2.6.2
+	 * @return {void}
+	 */
+	scrollToForm: function() {
+
+		/* Get initial variables to start computing boundaries */
+		var t           = this,
+			form        = t.getElement( 'new-post' ),
+			elemRect    = form.getBoundingClientRect(),
+			position    = (window.pageYOffset || document.scrollTop)  - (document.clientTop || 0),
+			destination = ( position + elemRect.top ),
+			negative    = ( destination < position ), // jshint ignore:line
+			adminbar    = t.getElement( 'wpadminbar'),
+			offset      = 0;
+
+		/* Offset by the adminbar */
+		if ( typeof ( adminbar ) !== 'undefined' ) {
+			offset = adminbar.scrollHeight;
+		}
+
+		/* Compute the difference, depending on direction */
+		/* jshint ignore:start */
+		distance = ( true === negative )
+			? ( position - destination )
+			: ( destination - position );
+
+		/* Do some math to compute the animation steps */
+		var vast       = ( distance > 800 ),
+			speed_step = vast ? 30 : 20,
+			speed      = Math.min( 12, Math.round( distance / speed_step ) ),
+			step       = Math.round( distance / speed_step ),
+			steps      = [],
+			timer      = 0;
+
+		/* Scroll up */
+		if ( true === negative ) {
+			while ( position > destination ) {
+				position -= step;
+
+				if ( position < destination ) {
+					position = destination;
+				}
+
+				steps.push( position - offset );
+
+				setTimeout( function() {
+					window.scrollTo( 0, steps.shift() );
+				}, timer * speed );
+
+				timer++;
+			}
+
+		/* Scroll down */
+		} else {
+			while ( position < destination ) {
+				position += step;
+
+				if ( position > destination ) {
+					position = destination;
+				}
+
+				steps.push( position - offset );
+
+				setTimeout( function() {
+					window.scrollTo( 0, steps.shift() );
+				}, timer * speed );
+
+				timer++;
+			}
+		}
+		/* jshint ignore:end */
+	},
+
+	/**
+	 * Get an element by ID
+	 *
+	 * @since 2.6.2
+	 * @param {string} e
+	 * @returns {HTMLElement} Element
+	 */
+	getElement: function (e) {
+		return document.getElementById(e);
+	},
+
+	/**
+	 * Remove the Editor
+	 *
+	 * @since 2.6.2
+	 * @returns {void}
+	 */
+	removeEditor: function () {
+
+		/* Bail to avoid error */
+		if ( typeof ( tinyMCE ) === 'undefined' ) {
+			return;
+		}
+
+		var tmce = tinyMCE.get( 'bbp_reply_content' );
+
+		if ( tmce && ! tmce.isHidden() ) {
+			this.mode = 'tmce';
+			tmce.remove();
+
+		} else {
+			this.mode = 'html';
+		}
+	},
+
+	/**
+	 * Add the Editor
+	 *
+	 * @since 2.6.2
+	 * @returns {void}
+	 */
+	addEditor: function () {
+
+		/* Bail to avoid error */
+		if ( typeof ( tinyMCE ) === 'undefined' ) {
+			return;
+		}
+
+		if ( 'tmce' === this.mode ) {
+			switchEditors.go( 'bbp_reply_content', 'tmce' );
+
+		} else if ( 'html' === this.mode ) {
+			switchEditors.go( 'bbp_reply_content', 'html' );
+		}
+	}
+};
Index: /tags/2.6.6/src/templates/index.php
===================================================================
--- /tags/2.6.6/src/templates/index.php	(revision 7162)
+++ /tags/2.6.6/src/templates/index.php	(revision 7162)
@@ -0,0 +1,7 @@
+<?php
+
+/**
+ * Do not put custom themes here. They will be deleted on bbPress updates.
+ *
+ * Keep custom bbPress themes in /wp-content/themes/
+ */
Index: /tags/2.6.6/tests/phpunit/bootstrap.php
===================================================================
--- /tags/2.6.6/tests/phpunit/bootstrap.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/bootstrap.php	(revision 7162)
@@ -0,0 +1,52 @@
+<?php
+
+// Define our constants
+echo "Defining constants...\n";
+require( dirname( __FILE__ ) . '/includes/define-constants.php' );
+
+echo "Ensure bbPress is an active plugin...\n";
+$GLOBALS['wp_tests_options'] = array(
+	'active_plugins' => array( 'bbpress/bbpress.php' ),
+);
+
+// Bail if test suite cannot be found
+if ( ! file_exists( WP_TESTS_DIR . '/includes/functions.php' ) ) {
+	die( "The WordPress PHPUnit test suite could not be found.\n" );
+} else {
+	echo "Loading WordPress PHPUnit test suite...\n";
+	require( WP_TESTS_DIR . '/includes/functions.php' );
+}
+
+/**
+ * Load the bbPress/PHPUnit test-suite loader
+ */
+function _load_loader() {
+
+	// Check if we're running the BuddyPress test suite
+	if ( defined( 'BBP_TESTS_BUDDYPRESS' ) ) {
+
+		// If BuddyPress is found, set it up and require it.
+		if ( defined( 'BP_TESTS_DIR' ) ) {
+			require BP_TESTS_DIR . '/includes/loader.php';
+		}
+	}
+
+	require( BBP_TESTS_DIR . '/includes/loader.php' );
+}
+tests_add_filter( 'muplugins_loaded', '_load_loader' );
+
+echo "Loading WordPress bootstrap...\n";
+require( WP_TESTS_DIR . '/includes/bootstrap.php' );
+
+echo "Loading bbPress testcase...\n";
+require( BBP_TESTS_DIR . '/includes/testcase.php' );
+require( BBP_TESTS_DIR . '/includes/factory.php' );
+
+if ( defined( 'BBP_TESTS_BUDDYPRESS' ) ) {
+	echo "Loading BuddyPress testcase...\n";
+	if ( defined( 'BP_TESTS_DIR' ) ) {
+		require( BP_TESTS_DIR . '/includes/testcase.php' );
+	}
+} else {
+	echo "Not running BuddyPress tests. To execute these, use -c tests/phpunit/buddypress.xml\n";
+}
Index: /tags/2.6.6/tests/phpunit/buddypress.xml
===================================================================
--- /tags/2.6.6/tests/phpunit/buddypress.xml	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/buddypress.xml	(revision 7162)
@@ -0,0 +1,33 @@
+<phpunit
+	bootstrap="bootstrap.php"
+	backupGlobals="false"
+	colors="true"
+	convertErrorsToExceptions="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	>
+	<php>
+		<const name="BBP_TESTS_BUDDYPRESS" value="1" />
+	</php>
+	<testsuites>
+		<testsuite>
+			<directory suffix=".php">./testcases/</directory>
+		</testsuite>
+	</testsuites>
+	<groups>
+		<include>
+			<group>buddypress</group>
+		</include>
+	</groups>
+	<listeners>
+		<listener class="SpeedTrapListener" file="tests/phpunit/includes/speed-trap-listener.php">
+			<arguments>
+				<array>
+					<element key="slowThreshold">
+						<integer>100</integer>
+					</element>
+				</array>
+			</arguments>
+		</listener>
+	</listeners>
+</phpunit>
Index: /tags/2.6.6/tests/phpunit/includes/define-constants.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/define-constants.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/define-constants.php	(revision 7162)
@@ -0,0 +1,62 @@
+<?php
+
+// Define constants used by bbPress test suite
+if ( ! defined( 'BBP_PLUGIN_DIR' ) ) {
+	define( 'BBP_PLUGIN_DIR', dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/' );
+}
+
+if ( ! defined( 'BBP_TESTS_DIR' ) ) {
+	define( 'BBP_TESTS_DIR', dirname( dirname( __FILE__ ) ) . '/' );
+}
+
+/**
+ * Determine where the WP test suite lives. Three options are supported:
+ *
+ * - Define a WP_DEVELOP_DIR environment variable, which points to a checkout
+ *   of the develop.svn.wordpress.org repository (this is recommended)
+ * - Define a WP_TESTS_DIR environment variable, which points to a checkout of
+ *   WordPress test suite
+ * - Assume that we are inside of a develop.svn.wordpress.org setup, and walk
+ *   up the directory tree
+ */
+if ( false !== getenv( 'WP_TESTS_DIR' ) ) {
+	define( 'WP_TESTS_DIR', getenv( 'WP_TESTS_DIR' ) );
+	define( 'WP_ROOT_DIR', WP_TESTS_DIR );
+} else {
+
+	// Support WP_DEVELOP_DIR, as used by some plugins
+	if ( false !== getenv( 'WP_DEVELOP_DIR' ) ) {
+		define( 'WP_ROOT_DIR', getenv( 'WP_DEVELOP_DIR' ) );
+	} else {
+		define( 'WP_ROOT_DIR', dirname( dirname( dirname( dirname( dirname( dirname( dirname( __DIR__ ) ) ) ) ) ) ) );
+	}
+
+	define( 'WP_TESTS_DIR', WP_ROOT_DIR . '/tests/phpunit' );
+}
+
+// Based on the tests directory, look for a config file
+// Standard develop.svn.wordpress.org setup
+if ( file_exists( WP_ROOT_DIR . '/wp-tests-config.php' ) ) {
+	define( 'WP_TESTS_CONFIG_PATH', WP_ROOT_DIR . '/wp-tests-config.php' );
+
+// Legacy unit-test.svn.wordpress.org setup
+} elseif ( file_exists( WP_TESTS_DIR . '/wp-tests-config.php' ) ) {
+	define( 'WP_TESTS_CONFIG_PATH', WP_TESTS_DIR . '/wp-tests-config.php' );
+
+// Environment variable exists and points to tests/phpunit of
+// develop.svn.wordpress.org setup
+} elseif ( file_exists( dirname( dirname( WP_TESTS_DIR ) ) . '/wp-tests-config.php' ) ) {
+	define( 'WP_TESTS_CONFIG_PATH', dirname( dirname( WP_TESTS_DIR ) ) . '/wp-tests-config.php' );
+
+// No test config found.
+} else {
+	die( "wp-tests-config.php could not be found.\n" );
+}
+
+// Determine whether BuddyPress is present.
+if ( ! defined( 'BP_TESTS_DIR' ) ) {
+	$wp_content_dir = dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) );
+	if ( file_exists( $wp_content_dir . '/buddypress/tests/phpunit/bootstrap.php' ) ) {
+		define( 'BP_TESTS_DIR', $wp_content_dir . '/buddypress/tests/phpunit' );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/includes/factory.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/factory.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/factory.php	(revision 7162)
@@ -0,0 +1,99 @@
+<?php
+
+class BBP_UnitTest_Factory extends WP_UnitTest_Factory {
+
+	public $forum = null;
+	public $topic = null;
+	public $reply = null;
+
+	public function __construct() {
+		parent::__construct();
+
+		$this->forum = new BBP_UnitTest_Factory_For_Forum( $this );
+		$this->topic = new BBP_UnitTest_Factory_For_Topic( $this );
+		$this->reply = new BBP_UnitTest_Factory_For_Reply( $this );
+	}
+}
+
+class BBP_UnitTest_Factory_For_Forum extends WP_UnitTest_Factory_For_Thing {
+
+	public function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'post_title'   => new WP_UnitTest_Generator_Sequence( 'Forum %s' ),
+			'post_content' => new WP_UnitTest_Generator_Sequence( 'Content of Forum %s' ),
+		);
+	}
+
+	public function create_object( $args ) {
+		$forum_meta = isset( $args['forum_meta'] ) ? $args['forum_meta'] : array();
+		unset( $args['forum_meta'] );
+		$forum_data = bbp_insert_forum( $args, $forum_meta );
+		return $forum_data;
+	}
+
+	public function update_object( $forum_id, $fields ) {
+		$fields['forum_id'] = $forum_id;
+		return bbp_update_forum( $fields );
+	}
+
+	public function get_object_by_id( $forum_id ) {
+		return bbp_get_forum( $forum_id );
+	}
+}
+
+class BBP_UnitTest_Factory_For_Topic extends WP_UnitTest_Factory_For_Thing {
+
+	public function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'post_title'   => new WP_UnitTest_Generator_Sequence( 'Topic %s' ),
+			'post_content' => new WP_UnitTest_Generator_Sequence( 'Content of Topic %s' ),
+		);
+	}
+
+	public function create_object( $args ) {
+		$topic_meta = isset( $args['topic_meta'] ) ? $args['topic_meta'] : array();
+		unset( $args['topic_meta'] );
+		$topic_data = bbp_insert_topic( $args, $topic_meta );
+		return $topic_data;
+	}
+
+	public function update_object( $topic_id, $fields ) {
+		$fields['topic_id'] = $topic_id;
+		return bbp_update_topic( $fields );
+	}
+
+	public function get_object_by_id( $topic_id ) {
+		return bbp_get_topic( $topic_id );
+	}
+}
+
+class BBP_UnitTest_Factory_For_Reply extends WP_UnitTest_Factory_For_Thing {
+
+	public function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'post_content' => new WP_UnitTest_Generator_Sequence( 'Content of Reply %s' ),
+		);
+	}
+
+	public function create_object( $args ) {
+		$reply_meta = isset( $args['reply_meta'] ) ? $args['reply_meta'] : array();
+		unset( $args['reply_meta'] );
+		$reply_data = bbp_insert_reply( $args, $reply_meta );
+		return $reply_data;
+	}
+
+	public function update_object( $reply_id, $fields ) {
+		$fields['reply_id'] = $reply_id;
+		return bbp_update_reply( $fields );
+	}
+
+	public function get_object_by_id( $reply_id ) {
+		return bbp_get_reply( $reply_id );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/includes/install.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/install.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/install.php	(revision 7162)
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Installs bbPress for the purpose of the unit-tests
+ */
+
+echo "Setting up WordPress test environment...\n";
+
+error_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT );
+
+$config_file_path = $argv[1];
+$tests_dir_path   = $argv[2];
+$multisite        = ! empty( $argv[3] );
+
+// Pull in the WordPress core test suite
+require_once $config_file_path;
+require_once $tests_dir_path . '/includes/functions.php';
+
+/**
+ * Include bbPress via `plugins_loaded` event
+ *
+ * This could maybe me earlier on `muplugins_loaded` but time will tell what
+ * works best for us.
+ */
+function _load_bbpress() {
+	echo "Loading bbPress via `/src/bbpress.php`...\n";
+	require dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/bbpress.php';
+}
+tests_add_filter( 'plugins_loaded', '_load_bbpress' );
+
+// Override some fussy global values
+$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
+$_SERVER['HTTP_HOST']       = WP_TESTS_DOMAIN;
+$_SERVER['REMOTE_ADDR']     = '127.0.0.1';
+
+// Fix PHP identity crisis
+$PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php';
+
+// Include WordPress
+echo "Loading WordPress via `wp-settings.php`...\n";
+require_once ABSPATH . '/wp-settings.php';
+
+// Fix fussy database settings
+$wpdb->query( 'SET storage_engine = INNODB' );
+$wpdb->select( DB_NAME, $wpdb->dbh );
+
+// Install bbPress
+echo "Installing bbPress...\n";
+bbp_version_bump();
Index: /tags/2.6.6/tests/phpunit/includes/loader.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/loader.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/loader.php	(revision 7162)
@@ -0,0 +1,9 @@
+<?php
+
+// Setup multisite global constant
+$multisite = (int) ( defined( 'WP_TESTS_MULTISITE') && WP_TESTS_MULTISITE );
+
+echo "Determining installation type...\n";
+system( WP_PHP_BINARY . ' ' . escapeshellarg( dirname( __FILE__ ) . '/install.php' ) . ' ' . escapeshellarg( WP_TESTS_CONFIG_PATH ) . ' ' . escapeshellarg( WP_TESTS_DIR ) . ' ' . $multisite );
+
+require dirname( __FILE__ ) . '/../../../src/bbpress.php';
Index: /tags/2.6.6/tests/phpunit/includes/speed-trap-listener.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/speed-trap-listener.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/speed-trap-listener.php	(revision 7162)
@@ -0,0 +1,326 @@
+<?php
+
+/**
+ * A PHPUnit TestListener that exposes your slowest running tests by outputting
+ * results directly to the console.
+ */
+class SpeedTrapListener implements PHPUnit_Framework_TestListener
+{
+    /**
+     * Internal tracking for test suites.
+     *
+     * Increments as more suites are run, then decremented as they finish. All
+     * suites have been run when returns to 0.
+     *
+     * @var integer
+     */
+    protected $suites = 0;
+
+    /**
+     * Time in milliseconds at which a test will be considered "slow" and be
+     * reported by this listener.
+     *
+     * @var int
+     */
+    protected $slowThreshold;
+
+    /**
+     * Number of tests to report on for slowness.
+     *
+     * @var int
+     */
+    protected $reportLength;
+
+    /**
+     * Collection of slow tests.
+     *
+     * @var array
+     */
+    protected $slow = array();
+
+    /**
+     * Construct a new instance.
+     *
+     * @param array $options
+     */
+    public function __construct(array $options = array())
+    {
+        $this->loadOptions($options);
+    }
+
+    /**
+     * An error occurred.
+     *
+     * @param PHPUnit_Framework_Test $test
+     * @param Exception              $e
+     * @param float                   $time
+     */
+    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
+    {
+    }
+
+    /**
+     * A warning occurred.
+     *
+     * @param PHPUnit_Framework_Test    $test
+     * @param PHPUnit_Framework_Warning $e
+     * @param float                     $time
+     * @since Method available since Release 5.1.0
+     */
+    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
+    {
+    }
+
+    /**
+     * A failure occurred.
+     *
+     * @param PHPUnit_Framework_Test                 $test
+     * @param PHPUnit_Framework_AssertionFailedError $e
+     * @param float                                   $time
+     */
+    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
+    {
+    }
+
+    /**
+     * Incomplete test.
+     *
+     * @param PHPUnit_Framework_Test $test
+     * @param Exception              $e
+     * @param float                   $time
+     */
+    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
+    {
+    }
+
+    /**
+     * Risky test.
+     *
+     * @param PHPUnit_Framework_Test $test
+     * @param Exception              $e
+     * @param float                   $time
+     * @since  Method available since Release 4.0.0
+     */
+    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
+    {
+    }
+
+    /**
+     * Skipped test.
+     *
+     * @param PHPUnit_Framework_Test $test
+     * @param Exception              $e
+     * @param float                   $time
+     */
+    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
+    {
+    }
+
+    /**
+     * A test started.
+     *
+     * @param PHPUnit_Framework_Test $test
+     */
+    public function startTest(PHPUnit_Framework_Test $test)
+    {
+    }
+
+    /**
+     * A test ended.
+     *
+     * @param PHPUnit_Framework_Test $test
+     * @param float                   $time
+     */
+    public function endTest(PHPUnit_Framework_Test $test, $time)
+    {
+        if (!$test instanceof PHPUnit_Framework_TestCase) return;
+
+        $time = $this->toMilliseconds($time);
+        $threshold = $this->getSlowThreshold($test);
+
+        if ($this->isSlow($time, $threshold)) {
+            $this->addSlowTest($test, $time);
+        }
+    }
+
+    /**
+     * A test suite started.
+     *
+     * @param PHPUnit_Framework_TestSuite $suite
+     */
+    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
+    {
+        $this->suites++;
+    }
+
+    /**
+     * A test suite ended.
+     *
+     * @param PHPUnit_Framework_TestSuite $suite
+     */
+    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
+    {
+        $this->suites--;
+
+        if (0 === $this->suites && $this->hasSlowTests()) {
+            arsort($this->slow); // Sort longest running tests to the top
+
+            $this->renderHeader();
+            $this->renderBody();
+            $this->renderFooter();
+        }
+    }
+
+    /**
+     * Whether the given test execution time is considered slow.
+     *
+     * @param int $time          Test execution time in milliseconds
+     * @param int $slowThreshold Test execution time at which a test should be considered slow (milliseconds)
+     * @return bool
+     */
+    protected function isSlow($time, $slowThreshold)
+    {
+        return $time >= $slowThreshold;
+    }
+
+    /**
+     * Stores a test as slow.
+     *
+     * @param PHPUnit_Framework_TestCase $test
+     * @param int                         $time Test execution time in milliseconds
+     */
+    protected function addSlowTest(PHPUnit_Framework_TestCase $test, $time)
+    {
+        $label = $this->makeLabel($test);
+
+        $this->slow[$label] = $time;
+    }
+
+    /**
+     * Whether at least one test has been considered slow.
+     *
+     * @return bool
+     */
+    protected function hasSlowTests()
+    {
+        return !empty($this->slow);
+    }
+
+    /**
+     * Convert PHPUnit's reported test time (microseconds) to milliseconds.
+     *
+     * @param float $time
+     * @return int
+     */
+    protected function toMilliseconds($time)
+    {
+        return (int) round($time * 1000);
+    }
+
+    /**
+     * Label for describing a test.
+     *
+     * @param PHPUnit_Framework_TestCase $test
+     * @return string
+     */
+    protected function makeLabel(PHPUnit_Framework_TestCase $test)
+    {
+        return sprintf('%s:%s', get_class($test), $test->getName());
+    }
+
+    /**
+     * Calculate number of slow tests to report about.
+     *
+     * @return int
+     */
+    protected function getReportLength()
+    {
+        return min(count($this->slow), $this->reportLength);
+    }
+
+    /**
+     * Find how many slow tests occurred that won't be shown due to list length.
+     *
+     * @return int Number of hidden slow tests
+     */
+    protected function getHiddenCount()
+    {
+        $total = count($this->slow);
+        $showing = $this->getReportLength($this->slow);
+
+        $hidden = 0;
+        if ($total > $showing) {
+            $hidden = $total - $showing;
+        }
+
+        return $hidden;
+    }
+
+    /**
+     * Renders slow test report header.
+     */
+    protected function renderHeader()
+    {
+        echo sprintf("\n\nYou should really fix these slow tests (>%sms)...\n", $this->slowThreshold);
+    }
+
+    /**
+     * Renders slow test report body.
+     */
+    protected function renderBody()
+    {
+        $slowTests = $this->slow;
+
+        $length = $this->getReportLength($slowTests);
+        for ($i = 1; $i <= $length; ++$i) {
+            $label = key($slowTests);
+            $time = array_shift($slowTests);
+
+            echo sprintf(" %s. %sms to run %s\n", $i, $time, $label);
+        }
+    }
+
+    /**
+     * Renders slow test report footer.
+     */
+    protected function renderFooter()
+    {
+        if ($hidden = $this->getHiddenCount($this->slow)) {
+            echo sprintf("...and there %s %s more above your threshold hidden from view", $hidden == 1 ? 'is' : 'are', $hidden);
+        }
+    }
+
+    /**
+     * Populate options into class internals.
+     *
+     * @param array $options
+     */
+    protected function loadOptions(array $options)
+    {
+        $this->slowThreshold = isset($options['slowThreshold']) ? $options['slowThreshold'] : 500;
+        $this->reportLength = isset($options['reportLength']) ? $options['reportLength'] : 10;
+    }
+
+    /**
+     * Get slow test threshold for given test. A TestCase can override the
+     * suite-wide slow threshold by using the annotation @slowThreshold with
+     * the threshold value in milliseconds.
+     *
+     * The following test will only be considered slow when its execution time
+     * reaches 5000ms (5 seconds):
+     *
+     * <code>
+     * @slowThreshold 5000
+     * public function testLongRunningProcess() {}
+     * </code>
+     *
+     * @param PHPUnit_Framework_TestCase $test
+     * @return int
+     */
+    protected function getSlowThreshold(PHPUnit_Framework_TestCase $test)
+    {
+        $ann = $test->getAnnotations();
+
+        return isset($ann['method']['slowThreshold'][0]) ? $ann['method']['slowThreshold'][0] : $this->slowThreshold;
+    }
+}
Index: /tags/2.6.6/tests/phpunit/includes/testcase.php
===================================================================
--- /tags/2.6.6/tests/phpunit/includes/testcase.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/includes/testcase.php	(revision 7162)
@@ -0,0 +1,162 @@
+<?php
+
+class BBP_UnitTestCase extends WP_UnitTestCase {
+
+	protected static $cached_SERVER_NAME = null;
+
+	/**
+	 * Fake WP mail globals, to avoid errors
+	 */
+	public static function setUpBeforeClass() {
+		add_filter( 'wp_mail',      array( 'BBP_UnitTestCase', 'setUp_wp_mail'    ) );
+		add_filter( 'wp_mail_from', array( 'BBP_UnitTestCase', 'tearDown_wp_mail' ) );
+	}
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->factory = new BBP_UnitTest_Factory;
+
+		if ( class_exists( 'BP_UnitTest_Factory' ) ) {
+			$this->bp_factory = new BP_UnitTest_Factory();
+		}
+
+		global $wpdb;
+
+		// Our default is ugly permalinks, so reset when needed.
+		global $wp_rewrite;
+		if ( $wp_rewrite->permalink_structure ) {
+			$this->set_permalink_structure();
+		}
+	}
+
+	public function tearDown() {
+		global $wpdb;
+
+		parent::tearDown();
+
+		if ( is_multisite() ) {
+			foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs WHERE blog_id != 1" ) as $blog_id ) {
+				wpmu_delete_blog( $blog_id, true );
+			}
+		}
+
+		foreach ( $wpdb->get_col( "SELECT ID FROM $wpdb->users WHERE ID != 1" ) as $user_id ) {
+			if ( is_multisite() ) {
+				wpmu_delete_user( $user_id );
+			} else {
+				wp_delete_user( $user_id );
+			}
+		}
+
+		$this->commit_transaction();
+	}
+
+	function clean_up_global_scope() {
+		parent::clean_up_global_scope();
+	}
+
+	function assertPreConditions() {
+		parent::assertPreConditions();
+	}
+
+	function go_to( $url ) {
+
+		parent::go_to( $url );
+
+		do_action( 'bbp_init' );
+	}
+
+	/**
+	 * WP's core tests use wp_set_current_user() to change the current
+	 * user during tests. BP caches the current user differently, so we
+	 * have to do a bit more work to change it
+	 */
+	public static function set_current_user( $user_id ) {
+		wp_set_current_user( $user_id );
+	}
+
+	/**
+	 * We can't use grant_super_admin() because we will need to modify
+	 * the list more than once, and grant_super_admin() can only be run
+	 * once because of its global check
+	 */
+	public function grant_super_admin( $user_id ) {
+		global $super_admins;
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$user = get_userdata( $user_id );
+		$super_admins[] = $user->user_login;
+	}
+
+	/**
+	 * We assume that the global can be wiped out
+	 *
+	 * @see grant_super_admin()
+	 */
+	public function restore_admins() {
+		unset( $GLOBALS['super_admins'] );
+	}
+
+	/**
+	 * Set up globals necessary to avoid errors when using wp_mail()
+	 */
+	public static function setUp_wp_mail( $args ) {
+		if ( isset( $_SERVER['SERVER_NAME'] ) ) {
+			self::$cached_SERVER_NAME = $_SERVER['SERVER_NAME'];
+		}
+
+		$_SERVER['SERVER_NAME'] = 'example.com';
+
+		// passthrough
+		return $args;
+	}
+
+	/**
+	 * Tear down globals set up in setUp_wp_mail()
+	 */
+	public static function tearDown_wp_mail( $args ) {
+		if ( ! empty( self::$cached_SERVER_NAME ) ) {
+			$_SERVER['SERVER_NAME'] = self::$cached_SERVER_NAME;
+			self::$cached_SERVER_NAME = '';
+		} else {
+			unset( $_SERVER['SERVER_NAME'] );
+		}
+
+		// passthrough
+		return $args;
+	}
+
+	/**
+	 * Commit a MySQL transaction.
+	 */
+	public static function commit_transaction() {
+		global $wpdb;
+		$wpdb->query( 'COMMIT;' );
+	}
+
+	/**
+	 * Utility method that resets permalinks and flushes rewrites.
+	 *
+	 * @since 2.6.0 bbPress (r5947)
+	 *
+	 * @global WP_Rewrite $wp_rewrite
+	 *
+	 * @param string $structure Optional. Permalink structure to set. Default empty.
+	 */
+	public function set_permalink_structure( $structure = '' ) {
+
+		// Use WP 4.4+'s version if it exists.
+		if ( method_exists( 'parent', 'set_permalink_structure' ) ) {
+			parent::set_permalink_structure( $structure );
+		} else {
+			global $wp_rewrite;
+
+			$wp_rewrite->init();
+			$wp_rewrite->set_permalink_structure( $structure );
+			$wp_rewrite->flush_rules();
+		}
+	}
+}
Index: /tags/2.6.6/tests/phpunit/multisite.xml
===================================================================
--- /tags/2.6.6/tests/phpunit/multisite.xml	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/multisite.xml	(revision 7162)
@@ -0,0 +1,33 @@
+<phpunit
+	bootstrap="bootstrap.php"
+	backupGlobals="false"
+	colors="true"
+	convertErrorsToExceptions="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	>
+	<php>
+		<const name="WP_TESTS_MULTISITE" value="1" />
+	</php>
+	<testsuites>
+		<testsuite>
+			<directory suffix=".php">./testcases/</directory>
+		</testsuite>
+	</testsuites>
+	<groups>
+		<exclude>
+			<group>buddypress</group>
+		</exclude>
+	</groups>
+	<listeners>
+		<listener class="SpeedTrapListener" file="tests/phpunit/includes/speed-trap-listener.php">
+			<arguments>
+				<array>
+					<element key="slowThreshold">
+						<integer>250</integer>
+					</element>
+				</array>
+			</arguments>
+		</listener>
+	</listeners>
+</phpunit>
Index: /tags/2.6.6/tests/phpunit/testcases/admin/tools.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/admin/tools.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/admin/tools.php	(revision 7162)
@@ -0,0 +1,801 @@
+<?php
+
+/**
+ * Tests for the admin tools functions.
+ *
+ * @group tools
+ */
+class BBP_Tests_Admin_Tools extends BBP_UnitTestCase {
+	protected $old_current_user = 0;
+
+	public function setUp() {
+		parent::setUp();
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
+		$this->keymaster_id = get_current_user_id();
+
+		bbp_set_user_role( $this->keymaster_id, bbp_get_keymaster_role() );
+
+		if ( ! function_exists( 'bbp_admin_repair_page' ) ) {
+			require_once BBP_PLUGIN_DIR . 'includes/admin/tools/repair.php';
+			require_once BBP_PLUGIN_DIR . 'includes/admin/tools/upgrade.php';
+		}
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair
+	 * @todo   Implement test_bbp_admin_repair().
+	 */
+	public function test_bbp_admin_repair() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_handler
+	 * @todo   Implement test_bbp_admin_repair_handler().
+	 */
+	public function test_bbp_admin_repair_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_tools_repair_help
+	 * @todo   Implement test_bbp_admin_tools_repair_help().
+	 */
+	public function test_bbp_admin_tools_repair_help() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_tools_reset_help
+	 * @todo   Implement test_bbp_admin_tools_reset_help().
+	 */
+	public function test_bbp_admin_tools_reset_help() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_tools_converter_help
+	 * @todo   Implement test_bbp_admin_tools_converter_help().
+	 */
+	public function test_bbp_admin_tools_converter_help() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_tools_feedback
+	 * @todo   Implement test_bbp_admin_tools_feedback().
+	 */
+	public function test_bbp_admin_tools_feedback() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_list
+	 * @todo   Implement test_bbp_admin_repair_list().
+	 */
+	public function test_bbp_admin_repair_list() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_topic_reply_count
+	 * @todo   Implement test_bbp_admin_repair_topic_reply_count().
+	 */
+	public function test_bbp_admin_repair_topic_reply_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_topic_voice_count
+	 */
+	public function test_bbp_admin_repair_topic_voice_count() {
+		$u = $this->factory->user->create_many( 2 );
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_author' => $u[0],
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_author' => $u[1],
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '3', $count );
+
+		// Delete the topic _bbp_voice_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_voice_count' ) );
+
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '0', $count );
+
+		// Repair the topic voice count meta.
+		bbp_admin_repair_topic_voice_count();
+
+		clean_post_cache( $t );
+
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '3', $count );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_topic_hidden_reply_count
+	 */
+	public function test_bbp_admin_repair_topic_hidden_reply_count() {
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_spam_reply( $r[0] );
+		bbp_unapprove_reply( $r[2] );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 2, $count );
+
+		// Delete the topic _bbp_reply_count_hidden meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_reply_count_hidden' ) );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		// Repair the topic hidden reply count meta.
+		bbp_admin_repair_topic_hidden_reply_count();
+
+		clean_post_cache( $t );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_group_forum_relationship
+	 * @todo   Implement test_bbp_admin_repair_group_forum_relationship().
+	 */
+	public function test_bbp_admin_repair_group_forum_relationship() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_forum_topic_count
+	 */
+	public function test_bbp_admin_repair_forum_topic_count() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_topic_count( $c );
+		bbp_update_forum_topic_count( $f );
+
+		// Category topic count.
+		$count = bbp_get_forum_topic_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count.
+		$count = bbp_get_forum_topic_count( $c, true, true );
+		$this->assertSame( 4, $count );
+
+		// Category topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+
+		// Forum topic count.
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 4, $count );
+
+		// Forum total topic count.
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 4, $count );
+
+		// Forum topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 0, $count );
+
+		bbp_spam_topic( $t[0] );
+		bbp_unapprove_topic( $t[2] );
+
+		// Category topic count.
+		$count = bbp_get_forum_topic_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count.
+		$count = bbp_get_forum_topic_count( $c, true, true );
+		$this->assertSame( 2, $count );
+
+		// Category topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+
+		// Forum topic count.
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		// Forum total topic count.
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		// Forum topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 2, $count );
+
+		// Delete the _bbp_total_topic_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_topic_count_hidden' ) );
+
+		// Delete the _bbp_total_topic_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_total_topic_count' ) );
+
+		// Delete the  _bbp_topic_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_topic_count' ) );
+
+		// Category topic count.
+		$count = bbp_get_forum_topic_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count.
+		$count = bbp_get_forum_topic_count( $c, true, true );
+		$this->assertSame( 0, $count );
+
+		// Category topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+
+		// Forum topic count.
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		// Forum total topic count.
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		// Forum topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 0, $count );
+
+		// Repair the forum topic count meta.
+		bbp_admin_repair_forum_topic_count();
+
+		// Category topic count.
+		$count = bbp_get_forum_topic_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count.
+		$count = bbp_get_forum_topic_count( $c, true, true );
+		$this->assertSame( 2, $count );
+
+		// Category topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+
+		// Forum topic count.
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		// Forum total topic count.
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		// Forum topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_forum_reply_count
+	 */
+	public function test_bbp_admin_repair_forum_reply_count() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_update_forum_reply_count( $c );
+		bbp_update_forum_reply_count( $f );
+
+		// Category reply count.
+		$count = bbp_get_forum_reply_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total reply count.
+		$count = bbp_get_forum_reply_count( $c, true, true );
+		$this->assertSame( 4, $count );
+
+		// Forum reply count.
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 4, $count );
+
+		// Forum total reply count.
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 4, $count );
+
+		bbp_spam_reply( $r[0] );
+		bbp_unapprove_reply( $r[2] );
+
+		// Category reply count.
+		$count = bbp_get_forum_reply_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total reply count.
+		$count = bbp_get_forum_reply_count( $c, true, true );
+		$this->assertSame( 2, $count );
+
+		// Forum reply count.
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		// Forum total reply count.
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		// Delete the _bbp_total_reply_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_total_reply_count' ) );
+
+		// Delete the _bbp_reply_count meta key.
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_reply_count' ) );
+
+		// Category reply count.
+		$count = bbp_get_forum_reply_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total reply count.
+		$count = bbp_get_forum_reply_count( $c, true, true );
+		$this->assertSame( 0, $count );
+
+		// Forum reply count.
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		// Forum total reply count.
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		// Repair the forum reply count meta.
+		bbp_admin_repair_forum_reply_count();
+
+		// Category reply count.
+		$count = bbp_get_forum_reply_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total reply count.
+		$count = bbp_get_forum_reply_count( $c, true, true );
+		$this->assertSame( 2, $count );
+
+		// Forum reply count.
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		// Forum total reply count.
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_topic_count
+	 * @todo   Implement test_bbp_admin_repair_user_topic_count().
+	 */
+	public function test_bbp_admin_repair_user_topic_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_reply_count
+	 * @todo   Implement test_bbp_admin_repair_user_reply_count().
+	 */
+	public function test_bbp_admin_repair_user_reply_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_favorites
+	 * @todo   Implement test_bbp_admin_repair_user_favorites().
+	 */
+	public function test_bbp_admin_repair_user_favorites() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_topic_subscriptions
+	 * @todo   Implement test_bbp_admin_repair_user_topic_subscriptions().
+	 */
+	public function test_bbp_admin_repair_user_topic_subscriptions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_forum_subscriptions
+	 * @todo   Implement test_bbp_admin_repair_user_forum_subscriptions().
+	 */
+	public function test_bbp_admin_repair_user_forum_subscriptions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_user_roles
+	 * @todo   Implement test_bbp_admin_repair_user_roles().
+	 */
+	public function test_bbp_admin_repair_user_roles() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_freshness
+	 * @todo   Implement test_bbp_admin_repair_freshness().
+	 */
+	public function test_bbp_admin_repair_freshness() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_sticky
+	 * @todo   Implement test_bbp_admin_repair_sticky().
+	 */
+	public function test_bbp_admin_repair_sticky() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_closed_topics
+	 * @todo   Implement test_bbp_admin_repair_closed_topics().
+	 */
+	public function test_bbp_admin_repair_closed_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_forum_visibility
+	 * @todo   Implement test_bbp_admin_repair_forum_visibility().
+	 */
+	public function test_bbp_admin_repair_forum_visibility() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_forum_meta
+	 */
+	public function test_bbp_admin_repair_forum_meta() {
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Forums should have an empty _bbp_forum_id meta key
+		$this->assertEquals( array( 0 => '0' ), get_post_meta( $f, '_bbp_forum_id', false ) );
+
+		// Topics should have a _bbp_forum_id meta key
+		$this->assertSame( $f, bbp_get_topic_forum_id( $t ) );
+
+		// Replies should have a _bbp_forum_id meta key
+		$this->assertSame( $f, bbp_get_reply_forum_id( $r ) );
+
+		// Delete the topic and reply _bbp_forum_id meta key
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_forum_id' ) );
+
+		// Check the _bbp_forum_id meta key is deleted
+		$this->assertEquals( array(), get_post_meta( $f, '_bbp_forum_id', false ) );
+		$this->assertEquals( array(), get_post_meta( $t, '_bbp_forum_id', false ) );
+		$this->assertEquals( array(), get_post_meta( $r, '_bbp_forum_id', false ) );
+
+		// Repair the forum meta
+		bbp_admin_repair_forum_meta();
+
+		clean_post_cache( $f );
+		clean_post_cache( $t );
+		clean_post_cache( $r );
+
+		// Forums should NOT have a _bbp_forum_id meta key
+		$this->assertEquals( array(), get_post_meta( $f, '_bbp_forum_id', false ) );
+
+		// Topics should have a _bbp_forum_id meta key
+		$this->assertEquals( array( $f ), get_post_meta( $t, '_bbp_forum_id', false ) );
+		$this->assertSame( $f, bbp_get_topic_forum_id( $t ) );
+
+		// Replies should have a _bbp_forum_id meta key
+		$this->assertEquals( array( $f ), get_post_meta( $r, '_bbp_forum_id', false ) );
+		$this->assertSame( $f, bbp_get_reply_forum_id( $r ) );
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_topic_meta
+	 * @todo   Implement test_bbp_admin_repair_topic_meta().
+	 */
+	public function test_bbp_admin_repair_topic_meta() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_repair_reply_menu_order
+	 * @todo   Implement test_bbp_admin_repair_reply_menu_order().
+	 */
+	public function test_bbp_admin_repair_reply_menu_order() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_upgrade_user_favorites
+	 */
+	public function test_bbp_admin_upgrade_user_favorites() {
+
+		$u = $this->factory->user->create_many( 2 );
+		$t = $this->factory->topic->create();
+
+		// Create 2.5.x user meta favorites
+		update_user_option( $u[0], '_bbp_favorites', $t );
+		update_user_option( $u[1], '_bbp_favorites', $t );
+
+		// Upgrade the user favorites.
+		bbp_admin_upgrade_user_favorites();
+
+		$expected            = array( $u[0], $u[1] );
+		$favoriters          = bbp_get_topic_favoriters( $t );
+		$post_meta_favorites = get_post_meta( $t, '_bbp_favorite', false );
+
+		$this->assertEqualSets( $expected, $favoriters );
+		$this->assertEqualSets( $expected, $post_meta_favorites );
+	}
+
+	/**
+	 * @covers ::bbp_admin_upgrade_user_topic_subscriptions
+	 */
+	public function test_bbp_admin_upgrade_user_topic_subscriptions() {
+
+		$u = $this->factory->user->create_many( 2 );
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Create 2.5.x user meta topic subscriptions.
+		update_user_option( $u[0], '_bbp_subscriptions', $t );
+		update_user_option( $u[1], '_bbp_subscriptions', $t );
+
+		// Upgrade the user topic subscriptions.
+		bbp_admin_upgrade_user_topic_subscriptions();
+
+		$expected            = array( $u[0], $u[1] );
+		$topic_subscribers   = bbp_get_topic_subscribers( $t );
+		$post_meta_topic_sub = get_post_meta( $t, '_bbp_subscription', false );
+
+		$this->assertEqualSets( $expected, $topic_subscribers );
+		$this->assertEqualSets( $expected, $post_meta_topic_sub );
+	}
+
+	/**
+	 * @covers ::bbp_admin_upgrade_user_forum_subscriptions
+	 */
+	public function test_bbp_admin_upgrade_user_forum_subscriptions() {
+
+		$u = $this->factory->user->create_many( 2 );
+
+		$f = $this->factory->forum->create();
+
+		// Create 2.5.x user meta forum subscriptions.
+		update_user_option( $u[0], '_bbp_forum_subscriptions', $f );
+		update_user_option( $u[1], '_bbp_forum_subscriptions', $f );
+
+		// Upgrade the user forum subscriptions.
+		bbp_admin_upgrade_user_forum_subscriptions();
+
+		$expected            = array( $u[0], $u[1] );
+		$forum_subscribers   = bbp_get_forum_subscribers( $f );
+		$post_meta_forum_sub = get_post_meta( $f, '_bbp_subscription', false );
+
+		$this->assertEqualSets( $expected, $forum_subscribers );
+		$this->assertEqualSets( $expected, $post_meta_forum_sub );
+	}
+
+	/**
+	 * @covers ::bbp_admin_reset
+	 * @todo   Implement test_bbp_admin_reset().
+	 */
+	public function test_bbp_admin_reset() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_admin_reset_handler
+	 * @todo   Implement test_bbp_admin_reset_handler().
+	 */
+	public function test_bbp_admin_reset_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/common/formatting.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/common/formatting.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/common/formatting.php	(revision 7162)
@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * @group common
+ * @group functions
+ * @group bbp_make_clickable
+ */
+class BBP_Tests_Common_Functions_Make_Clickable extends BBP_UnitTestCase {
+
+	/**
+	 * @group bbp_make_clickable_misses
+	 * @group bbp_make_clickable_single
+	 * @covers ::bbp_make_clickable
+	 */
+	public function test_bbp_make_clickable_single_mention_misses() {
+		$u1 = $this->factory->user->create( array(
+			'user_login'    => 'foobarbaz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		add_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+
+		// Create the link to the user's profile
+		$user   = get_userdata( $u1 );
+		$url    = bbp_get_user_profile_url( $user->ID );
+		$anchor = '<a href="%1$s" class="">@%2$s</a>';
+		$name   = $user->user_nicename;
+		$link   = sprintf( $anchor, esc_url( $url ), esc_html( $name ) );
+		$link   = bbp_rel_nofollow( $link );
+
+		// mentions inside links, should not be replaced
+		$text                    = "Send messages to <a href='mailto:mail@%s.com'>Foo Bar Baz</a>";
+		$at_name_in_mailto       = sprintf( $text, $name );
+		$at_name_in_mailto_final = sprintf( $text, $name );
+		$this->assertEquals( $at_name_in_mailto_final, bbp_make_clickable( $at_name_in_mailto ) );
+
+		// mentions inside links, should not be replaced
+		$text                  = "Send messages to <a href='@%s'>Foo Bar Baz</a>";
+		$at_name_in_href       = sprintf( $text, $name );
+		$at_name_in_href_final = sprintf( $text, $name );
+		$this->assertEquals( $at_name_in_href_final, bbp_make_clickable( $at_name_in_href ) );
+
+		// mentions inside links (with an external match) should not be replaced inside href ever
+		$at_name_in_anchor       = sprintf( "Send messages to <a href='@%s'>@%s</a>", $name, $name );
+		$at_name_in_anchor_final = sprintf( "Send messages to <a href='@%s'>@%s</a>", $name, $name );
+		$this->assertEquals( $at_name_in_anchor_final, bbp_make_clickable( $at_name_in_anchor ) );
+
+		// mentions inside links (with an external match) should not be replaced inside href ever
+		$at_name_in_anchor_matched       = sprintf( "Send messages to <a href='@%s'>@%s</a> @%s", $name, $name, $name );
+		$at_name_in_anchor_matched_final = sprintf( "Send messages to <a href='@%s'>@%s</a> %s", $name, $name, $link );
+		$this->assertEquals( $at_name_in_anchor_matched_final, bbp_make_clickable( $at_name_in_anchor_matched ) );
+
+		// mentions inside attributes, should not be replaced
+		$text                  = '<a href=" @%s ................................ @%s @%s">@%s</a>';
+		$at_name_in_attr       = sprintf( $text, $name, $name, $name, $name );
+		$at_name_in_attr_final = sprintf( $text, $name, $name, $name, $name );
+		$this->assertEquals( $at_name_in_attr_final, bbp_make_clickable( $at_name_in_attr ) );
+
+		// mentions hugged by brackets, should not be replaced
+		$text                    = "<@%s>";
+		$at_name_in_hugged       = sprintf( $text, $name );
+		$at_name_in_hugged_final = sprintf( $text, $name );
+		$this->assertEquals( $at_name_in_hugged_final, bbp_make_clickable( $at_name_in_hugged ) );
+
+		// mentions between brackets, should not be replaced even when linked after
+		$at_name_between       = sprintf( "foo < %s > @%s", $name, $link );
+		$at_name_between_final = sprintf( "foo < %s > @%s", $name, $link );
+		$this->assertEquals( $at_name_between_final, bbp_make_clickable( $at_name_between ) );
+
+		remove_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+	}
+
+	/**
+	 * @group bbp_make_clickable_hits
+	 * @group bbp_make_clickable_single
+	 * @covers ::bbp_make_clickable
+	 */
+	public function test_bbp_make_clickable_single_mention_hits() {
+		$u1 = $this->factory->user->create( array(
+			'user_login'    => 'foobarbaz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		add_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+
+		// Create the link to the user's profile
+		$user   = get_userdata( $u1 );
+		$url    = bbp_get_user_profile_url( $user->ID );
+		$anchor = '<a href="%1$s" class="">@%2$s</a>';
+		$name   = $user->user_nicename;
+		$link   = sprintf( $anchor, esc_url( $url ), esc_html( $user->user_nicename ) );
+		$link   = bbp_rel_nofollow( $link );
+
+		// mentions inside links (with an external match) should not be replaced inside href ever
+		$at_name_in_href_matched       = sprintf( "Send messages to <a href='@%s'>Foo Bar Baz</a>@%s @%s", $name, $name, $name );
+		$at_name_in_href_matched_final = sprintf( "Send messages to <a href='@%s'>Foo Bar Baz</a>%s %s", $name, $link, $link );
+		$this->assertEquals( $at_name_in_href_matched_final, bbp_make_clickable( $at_name_in_href_matched ) );
+
+		// mentions inside linked text (with an external match) should not be linked
+		$at_name_in_link_matched       = sprintf( "<a href='https://twitter.com/%s'>@%s</a>@%s @%s", $name, $name, $name, $name );
+		$at_name_in_link_matched_final = sprintf( "<a href='https://twitter.com/%s'>@%s</a>%s %s", $name, $name, $link, $link );
+		$this->assertEquals( $at_name_in_link_matched_final, bbp_make_clickable( $at_name_in_link_matched ) );
+
+		// mentions after greater-than bracket, should be replaced
+		$at_name_after_greater_than       = sprintf( "foo > @%s", $name );
+		$at_name_after_greater_than_final = sprintf( "foo > %s", $link );
+		$this->assertEquals( $at_name_after_greater_than_final, bbp_make_clickable( $at_name_after_greater_than ) );
+
+		// mentions after less-than bracket, should be replaced
+		$at_name_after_less_than       = sprintf( "foo < @%s", $name );
+		$at_name_after_less_than_final = sprintf( "foo < %s", $link );
+		$this->assertEquals( $at_name_after_less_than_final, bbp_make_clickable( $at_name_after_less_than ) );
+
+		// mentions at end of normal text, should be replaced
+		$at_name_at_end       = sprintf( 'Hello @%s', $name );
+		$at_name_at_end_final = sprintf( 'Hello %s', $link );
+		$this->assertEquals( $at_name_at_end_final, bbp_make_clickable( $at_name_at_end ) );
+
+		// mentions at start of normal text, should be replaced
+		$at_name_at_start       = sprintf( '@%s, hello', $name );
+		$at_name_at_start_final = sprintf( '%s, hello', $link );
+		$this->assertEquals( $at_name_at_start_final, bbp_make_clickable( $at_name_at_start ) );
+
+		// mentions surrounded by square brackets
+		$at_name_square_brackets       = sprintf( '[@%s] hello', $name );
+		$at_name_square_brackets_final = sprintf( '[%s] hello', $link );
+		$this->assertEquals( $at_name_square_brackets_final, bbp_make_clickable( $at_name_square_brackets ) );
+
+		// mentions surrounded by round brackets
+		$at_name_round_brackets       = sprintf( '(@%s) hello', $name );
+		$at_name_round_brackets_final = sprintf( '(%s) hello', $link );
+		$this->assertEquals( $at_name_round_brackets_final, bbp_make_clickable( $at_name_round_brackets ) );
+
+		// mention is all text, should be replaced
+		$at_name_is_text       = sprintf( '@%s', $name );
+		$at_name_is_text_final = sprintf( '%s', $link );
+		$this->assertEquals( $at_name_is_text_final, bbp_make_clickable( $at_name_is_text ) );
+
+		// mention followed by colon, should be replaced
+		$at_name_colon       = sprintf( 'Hey @%s: hello', $name );
+		$at_name_colon_final = sprintf( 'Hey %s: hello', $link );
+		$this->assertEquals( $at_name_colon_final, bbp_make_clickable( $at_name_colon ) );
+
+		// mention followed by comma, should be replaced
+		$at_name_comma       = sprintf( 'Hey @%s, hello', $name );
+		$at_name_comma_final = sprintf( 'Hey %s, hello', $link );
+		$this->assertEquals( $at_name_comma_final, bbp_make_clickable( $at_name_comma ) );
+
+		// Don't link non-existent users
+		$text = "Don't link @non @existent @users";
+		$this->assertSame( $text, bbp_make_clickable( $text ) );
+
+		remove_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+	}
+
+	/**
+	 * @group bbp_make_clickable_hits
+	 * @group bbp_make_clickable_multiple
+	 * @covers ::bbp_make_clickable
+	 */
+	public function test_bbp_make_clickable_multiple_mention_hits() {
+		$u1 = $this->factory->user->create( array(
+			'user_login'    => 'foobarbaz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		$u2 = $this->factory->user->create( array(
+			'user_login'    => 'foo2',
+			'user_nicename' => 'foo2',
+		) );
+
+		add_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+
+		// Create the link to the user's profile
+		$user_1   = get_userdata( $u1 );
+		$url_1    = bbp_get_user_profile_url( $user_1->ID );
+		$anchor_1 = '<a href="%1$s" class="">@%2$s</a>';
+		$name_1   = $user_1->user_nicename;
+		$link_1   = sprintf( $anchor_1, esc_url( $url_1 ), esc_html( $name_1 ) );
+		$link_1   = bbp_rel_nofollow( $link_1 );
+
+		$user_2   = get_userdata( $u2 );
+		$url_2    = bbp_get_user_profile_url( $user_2->ID );
+		$anchor_2 = '<a href="%1$s" class="">@%2$s</a>';
+		$name_2   = $user_2->user_nicename;
+		$link_2   = sprintf( $anchor_2, esc_url( $url_2 ), esc_html( $name_2 ) );
+		$link_2   = bbp_rel_nofollow( $link_2 );
+
+		// Multiples
+		$at_name_in_mailto       = sprintf( "Send messages to @%s, @%s.", $name_1, $name_2 );
+		$at_name_in_mailto_final = sprintf( "Send messages to %s, %s.",   $link_1, $link_2 );
+		$this->assertEquals( $at_name_in_mailto_final, bbp_make_clickable( $at_name_in_mailto ) );
+
+		// Two names separated by `&`
+		$at_names_inbetween_ampersand       = sprintf( "Oh hi @%s & @%s.", $name_1, $name_2 );
+		$at_names_inbetween_ampersand_final = sprintf( "Oh hi %s & %s.",   $link_1, $link_2 );
+		$this->assertEquals( $at_names_inbetween_ampersand_final, bbp_make_clickable( $at_names_inbetween_ampersand ) );
+
+		// Two names separated by `and`
+		$at_names_inbetween_and       = sprintf( "Oh hi @%s and @%s.", $name_1, $name_2 );
+		$at_names_inbetween_and_final = sprintf( "Oh hi %s and %s.",   $link_1, $link_2 );
+		$this->assertEquals( $at_names_inbetween_and_final, bbp_make_clickable( $at_names_inbetween_and ) );
+
+		remove_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+	}
+
+	/**
+	 * @group bbp_make_clickable_misses
+	 * @group bbp_make_clickable_multiple
+	 * @covers ::bbp_make_clickable
+	 */
+	public function test_bbp_make_clickable_multiple_mention_misses() {
+		$u1 = $this->factory->user->create( array(
+			'user_login'    => 'foobarbaz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		$u2 = $this->factory->user->create( array(
+			'user_login'    => 'foo2',
+			'user_nicename' => 'foo2',
+		) );
+
+		add_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+
+		// Create the link to the user's profile
+		$user_1   = get_userdata( $u1 );
+		$url_1    = bbp_get_user_profile_url( $user_1->ID );
+		$anchor_1 = '<a href="%1$s" class="">@%2$s</a>';
+		$name_1   = $user_1->user_nicename;
+		$link_1   = sprintf( $anchor_1, esc_url( $url_1 ), esc_html( $name_1 ) );
+		$link_1   = bbp_rel_nofollow( $link_1 );
+
+		$user_2   = get_userdata( $u2 );
+		$url_2    = bbp_get_user_profile_url( $user_2->ID );
+		$anchor_2 = '<a href="%1$s" class="">@%2$s</a>';
+		$name_2   = $user_2->user_nicename;
+		$link_2   = sprintf( $anchor_2, esc_url( $url_2 ), esc_html( $name_2 ) );
+		$link_2   = bbp_rel_nofollow( $link_2 );
+
+		// Multiples
+		$at_name_in_mailto       = sprintf( "Send messages to @%s, @non1, @%s, @non2.", $name_1, $name_2 );
+		$at_name_in_mailto_final = sprintf( "Send messages to %s, @non1, %s, @non2.",   $link_1, $link_2 );
+		$this->assertEquals( $at_name_in_mailto_final, bbp_make_clickable( $at_name_in_mailto ) );
+
+		remove_filter( 'bbp_make_mentions_clickable_classes', '__return_empty_array' );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/common/functions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/common/functions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/common/functions.php	(revision 7162)
@@ -0,0 +1,1288 @@
+<?php
+/**
+ * Tests for the common functions.
+ *
+ * @group common
+ * @group functions
+ */
+
+class BBP_Tests_Common_Functions extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_number_format
+	 * @todo   Implement test_bbp_number_format().
+	 */
+	public function test_bbp_number_format() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_number_format_i18n
+	 * @todo   Implement test_bbp_number_format_i18n().
+	 */
+	public function test_bbp_number_format_i18n() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_convert_date
+	 * @todo   Implement test_bbp_convert_date().
+	 */
+	public function test_bbp_convert_date() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_years_months() {
+		$now = time();
+		$then = $now - ( 3 * YEAR_IN_SECONDS ) - ( 3 * 30 * DAY_IN_SECONDS );
+		$since = '3 years, 3 months ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_year_month() {
+		$now = time();
+		$then = $now - YEAR_IN_SECONDS - ( 1 * 30 * DAY_IN_SECONDS );
+		$since = '1 year, 1 month ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_years_nomonths() {
+		$now = time();
+		$then = $now - ( 3 * YEAR_IN_SECONDS );
+		$since = '3 years ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_year_nomonths() {
+		$now = time();
+		$then = $now - YEAR_IN_SECONDS ;
+		$since = '1 year ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_months_weeks() {
+		$now = time();
+		$then = $now - ( 3 * 30 * DAY_IN_SECONDS ) - ( 3 * WEEK_IN_SECONDS );
+		$since = '3 months, 3 weeks ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_month_week() {
+		$now = time();
+		$then = $now - ( 1 * 30 * DAY_IN_SECONDS ) - ( 1 * WEEK_IN_SECONDS );
+		$since = '1 month, 1 week ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_months_noweeks() {
+		$now = time();
+		$then = $now - ( 3 * 30 * DAY_IN_SECONDS );
+		$since = '3 months ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_month_noweeks() {
+		$now = time();
+		$then = $now - ( 1 * 30 * DAY_IN_SECONDS );
+		$since = '1 month ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_weeks_days() {
+		$now = time();
+		$then = $now - ( 3 * WEEK_IN_SECONDS ) - ( 3 * DAY_IN_SECONDS );
+		$since = '3 weeks, 3 days ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_week_day() {
+		$now = time();
+		$then = $now - ( 1 * WEEK_IN_SECONDS ) - ( 1 * DAY_IN_SECONDS );
+		$since = '1 week, 1 day ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_weeks_nodays() {
+		$now = time();
+		$then = $now - ( 3 * WEEK_IN_SECONDS );
+		$since = '3 weeks ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_week_nodays() {
+		$now = time();
+		$then = $now - ( 1 * WEEK_IN_SECONDS );
+		$since = '1 week ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_days_hours() {
+		$now = time();
+		$then = $now - ( 3 * DAY_IN_SECONDS ) - ( 3 * HOUR_IN_SECONDS );
+		$since = '3 days, 3 hours ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_day_hour() {
+		$now = time();
+		$then = $now - ( 1 * DAY_IN_SECONDS ) - ( 1 * HOUR_IN_SECONDS );
+		$since = '1 day, 1 hour ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_days_nohours() {
+		$now = time();
+		$then = $now - ( 3 * DAY_IN_SECONDS );
+		$since = '3 days ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_day_nohours() {
+		$now = time();
+		$then = $now - ( 1 * DAY_IN_SECONDS );
+		$since = '1 day ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_hours_minutes() {
+		$now = time();
+		$then = $now - ( 3 * HOUR_IN_SECONDS ) - ( 3 * MINUTE_IN_SECONDS );
+		$since = '3 hours, 3 minutes ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_hour_minute() {
+		$now = time();
+		$then = $now - ( 1 * HOUR_IN_SECONDS ) - ( 1 * MINUTE_IN_SECONDS );
+		$since = '1 hour, 1 minute ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_hours_nominutes() {
+		$now = time();
+		$then = $now - ( 3 * HOUR_IN_SECONDS );
+		$since = '3 hours ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_hour_nominutes() {
+		$now = time();
+		$then = $now - ( 1 * HOUR_IN_SECONDS );
+		$since = '1 hour ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_minutes_seconds() {
+		$now = time();
+		$then = $now - ( 3 * MINUTE_IN_SECONDS ) - 3;
+		$since = '3 minutes ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_minutes_noseconds() {
+		$now = time();
+		$then = $now - ( 3 * MINUTE_IN_SECONDS );
+		$since = '3 minutes ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_minute_noseconds() {
+		$now = time();
+		$then = $now - ( 1 * MINUTE_IN_SECONDS );
+		$since = '1 minute ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_seconds() {
+		$now = time();
+		$then = $now - 3;
+		$since = '3 seconds ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_second() {
+		$now = time();
+		$then = $now - 1;
+		$since = '1 second ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_rightnow() {
+		$now = time();
+		$then = $now;
+		$since = 'right now';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_future() {
+		$now = time();
+		$then = $now + 100;
+		$since = 'sometime ago';
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertEquals( $since, bbp_get_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_timezone_minute_ago() {
+		$now = time();
+		$then = $now - ( 1 * MINUTE_IN_SECONDS );
+		$since = '1 minute ago';
+
+		// Backup timezone.
+		$tz_backup = date_default_timezone_get();
+
+		// Set timezone to something other than UTC.
+		date_default_timezone_set( 'Europe/Paris' );
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertSame( $since, bbp_get_time_since( $then, $now, $gmt = false ) );
+
+		// Revert timezone back to normal.
+		if ( $tz_backup ) {
+			date_default_timezone_set( $tz_backup );
+		}
+	}
+
+	/**
+	 * @covers ::bbp_time_since
+	 * @covers ::bbp_get_time_since
+	 */
+	public function test_bbp_time_since_timezone() {
+		$now = time();
+		$then = $now - ( 1 * HOUR_IN_SECONDS );
+		$since = '1 hour ago';
+
+		// Backup timezone.
+		$tz_backup = date_default_timezone_get();
+
+		// Set timezone to something other than UTC.
+		date_default_timezone_set( 'Europe/Paris' );
+
+		// Output.
+		$this->expectOutputString( $since );
+		bbp_time_since( $then, $now );
+
+		// Formatted.
+		$this->assertSame( $since, bbp_get_time_since( $then, $now, true ) );
+
+		// Revert timezone back to normal.
+		if ( $tz_backup ) {
+			date_default_timezone_set( $tz_backup );
+		}
+	}
+
+	/**
+	 * @covers ::bbp_format_revision_reason
+	 * @todo   Implement test_bbp_format_revision_reason().
+	 */
+	public function test_bbp_format_revision_reason() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_redirect_to
+	 * @todo   Implement test_bbp_get_redirect_to().
+	 */
+	public function test_bbp_get_redirect_to() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_add_view_all
+	 * @todo   Implement test_bbp_add_view_all().
+	 */
+	public function test_bbp_add_view_all() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_remove_view_all
+	 * @todo   Implement test_bbp_remove_view_all().
+	 */
+	public function test_bbp_remove_view_all() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_view_all
+	 * @todo   Implement test_bbp_get_view_all().
+	 */
+	public function test_bbp_get_view_all() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_paged
+	 * @todo   Implement test_bbp_get_paged().
+	 */
+	public function test_bbp_get_paged() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_fix_post_author
+	 * @todo   Implement test_bbp_fix_post_author().
+	 */
+	public function test_bbp_fix_post_author() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_before_5_minutes() {
+		update_option( '_bbp_edit_lock', 5 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// Before
+		$result = bbp_past_edit_lock( '4 minutes 59 seconds ago UTC' );
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_on_5_minutes() {
+		update_option( '_bbp_edit_lock', 5 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// On
+		$result = bbp_past_edit_lock( '5 minutes ago UTC' );
+		$this->assertTrue( $result );
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_after_5_minutes() {
+		update_option( '_bbp_edit_lock', 5 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// After
+		$result = bbp_past_edit_lock( '5 minutes 1 second ago UTC' );
+		$this->assertTrue( $result );
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_before_0_minutes() {
+		update_option( '_bbp_edit_lock', 0 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// Before
+		$result = bbp_past_edit_lock( '4 minutes 59 seconds ago UTC' );
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_on_0_minutes() {
+		update_option( '_bbp_edit_lock', 0 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// On
+		$result = bbp_past_edit_lock( '5 minutes ago UTC' );
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @group  locking
+	 * @covers ::bbp_past_edit_lock
+	 */
+	public function test_bbp_past_edit_lock_after_0_minutes() {
+		update_option( '_bbp_edit_lock', 0 );
+		update_option( '_bbp_allow_content_edit', true );
+
+		// After
+		$result = bbp_past_edit_lock( '5 minutes 1 second ago UTC' );
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_get_statistics
+	 * @todo   Implement test_bbp_get_statistics().
+	 */
+	public function test_bbp_get_statistics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_filter_anonymous_post_data
+	 * @todo   Implement test_bbp_filter_anonymous_post_data().
+	 */
+	public function test_bbp_filter_anonymous_post_data() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_for_duplicate
+	 * @todo   Implement test_bbp_check_for_duplicate().
+	 */
+	public function test_bbp_check_for_duplicate() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_for_flood
+	 * @todo   Implement test_bbp_check_for_flood().
+	 */
+	public function test_bbp_check_for_flood() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_bbp_check_for_moderation() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'Beware, there maybe bees hibernating.';
+
+		update_option( 'moderation_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+
+		update_option( 'moderation_keys',"foo\nbar" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertTrue( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_url_moderation_check() {
+		$u = $this->factory->user->create( array(
+			'user_url'   => 'http://example.net/banned',
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'moderation_keys',"http://example.net/banned\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_email_moderation_check() {
+		$u = $this->factory->user->create( array(
+			'user_email' => 'banned@example.net',
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'moderation_keys',"banned@example.net\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_ip_moderation_check() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'moderation_keys',"127.0.0.1\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_true_for_moderators_to_bypass_moderation_check() {
+		// Create a moderator user.
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'subscriber' ) ) );
+		$this->moderator_id = get_current_user_id();
+		bbp_set_user_role( $this->moderator_id, bbp_get_moderator_role() );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => bbp_get_current_user_id(),
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'moderation_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertTrue( $result );
+
+		// Retore the original user.
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_when_link_count_exceeds_comment_max_links_setting() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'This is a post with <a href="http://example.com">multiple</a> <a href="http://bob.example.com">links</a>.';
+
+		update_option( 'comment_max_links', 2 );
+		$results = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+		$this->assertFalse( $results );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_true_when_link_count_does_not_exceed_comment_max_links_setting() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'This is a post with <a href="http://example.com">multiple</a> <a href="http://bob.example.com">links</a>.';
+
+		update_option( 'comment_max_links', 3 );
+		$results = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+		$this->assertTrue( $results );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_when_link_matches_moderation_keys() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'Beware, there maybe bees <a href="http://example.com/hibernating/>buzzing</a>, buzzing.';
+
+		update_option( 'moderation_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_when_html_wrapped_content_matches_moderation_keys() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees <strong>hiber</strong><em>nating</em>.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'moderation_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_bbp_check_for_moderation_strict() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'Beware, they maybe bees hibernating.';
+
+		update_option( 'blacklist_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+
+		update_option( 'blacklist_keys',"foo\nbar" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertTrue( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_url_strict_moderation_check() {
+		$u = $this->factory->user->create( array(
+			'user_url'   => 'http://example.net/banned',
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"http://example.net/banned\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_email_strict_moderation_check() {
+		$u = $this->factory->user->create( array(
+			'user_email' => 'banned@example.net',
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"banned@example.net\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_user_ip_strict_moderation_check() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"127.0.0.1\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_for_moderators_to_bypass_strict_moderation_check() {
+		// Create a moderator user.
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'subscriber' ) ) );
+		$this->moderator_id = get_current_user_id();
+		bbp_set_user_role( $this->moderator_id, bbp_get_moderator_role() );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => bbp_get_current_user_id(),
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+
+		// Retore the original user.
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_true_for_keymasterss_to_bypass_strict_moderation_check() {
+		// Create a keymaster user.
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'subscriber' ) ) );
+		$this->keymaster_id = get_current_user_id();
+		bbp_set_user_role( $this->keymaster_id, bbp_get_keymaster_role() );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => bbp_get_current_user_id(),
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees hibernating.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertTrue( $result );
+
+		// Retore the original user.
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_when_link_matches_strict_moderation_keys() {
+		$anonymous_data = false;
+		$author_id      = 0;
+		$title          = 'Sting';
+		$content        = 'Beware, there maybe bees <a href="http://example.com/hibernating/>buzzing</a>, buzzing.';
+
+		update_option( 'blacklist_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_check_for_moderation
+	 */
+	public function test_should_return_false_when_html_wrapped_content_matches_strict_moderation_keys() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+			'post_title' => 'Sting',
+			'post_content' => 'Beware, there maybe bees <strong>hiber</strong><em>nating</em>.',
+		) );
+
+		$anonymous_data = false;
+		$author_id      = bbp_get_topic_author_id( $t );
+		$title          = bbp_get_topic_title( $t );
+		$content        = bbp_get_topic_content( $t );
+
+		update_option( 'blacklist_keys',"hibernating\nfoo" );
+
+		$result = bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @covers ::bbp_get_do_not_reply_address
+	 */
+	public function test_bbp_get_do_not_reply_address() {
+
+		$_SERVER['SERVER_NAME'] = 'example.org';
+		$address = bbp_get_do_not_reply_address();
+		$this->assertEquals( 'noreply@example.org', $address );
+
+		$_SERVER['SERVER_NAME'] = 'www.example.org';
+		$address = bbp_get_do_not_reply_address();
+		$this->assertEquals( 'noreply@example.org', $address );
+
+		$_SERVER['SERVER_NAME'] = 'subdomain.example.org';
+		$address = bbp_get_do_not_reply_address();
+		$this->assertEquals( 'noreply@subdomain.example.org', $address );
+
+		$_SERVER['SERVER_NAME'] = 'www.subdomain.example.org';
+		$address = bbp_get_do_not_reply_address();
+		$this->assertEquals( 'noreply@subdomain.example.org', $address );
+
+		// Reset server name.
+		$_SERVER['SERVER_NAME'] = 'example.org';
+	}
+
+	/**
+	 * @covers ::bbp_notify_topic_subscribers
+	 * @todo   Implement test_bbp_notify_topic_subscribers().
+	 */
+	public function test_bbp_notify_topic_subscribers() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_notify_forum_subscribers
+	 * @todo   Implement test_bbp_notify_forum_subscribers().
+	 */
+	public function test_bbp_notify_forum_subscribers() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_notify_subscribers
+	 * @todo   Implement test_bbp_notify_subscribers().
+	 */
+	public function test_bbp_notify_subscribers() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_logout_url
+	 * @todo   Implement test_bbp_logout_url().
+	 */
+	public function test_bbp_logout_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_parse_args
+	 * @todo   Implement test_bbp_parse_args().
+	 */
+	public function test_bbp_parse_args() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_global_post_field
+	 * @todo   Implement test_bbp_get_global_post_field().
+	 */
+	public function test_bbp_get_global_post_field() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_verify_nonce_request
+	 * @todo   Implement test_bbp_verify_nonce_request().
+	 */
+	public function test_bbp_verify_nonce_request() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_request_feed_trap
+	 * @todo   Implement test_bbp_request_feed_trap().
+	 */
+	public function test_bbp_request_feed_trap() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_page_by_path
+	 * @todo   Implement test_bbp_get_page_by_path().
+	 */
+	public function test_bbp_get_page_by_path() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_set_404
+	 * @todo   Implement test_bbp_set_404().
+	 */
+	public function test_bbp_set_404() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/common/query.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/common/query.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/common/query.php	(revision 7162)
@@ -0,0 +1,373 @@
+<?php
+
+/**
+ * Tests for the common query functions.
+ *
+ * @group common
+ * @group functions
+ * @group query
+ */
+class BBP_Tests_Common_Functions_Query extends BBP_UnitTestCase {
+
+ 	/**
+	 * @covers ::bbp_query_post_parent__in
+	 * @todo   Implement test_bbp_query_post_parent__in().
+	 */
+	public function test_bbp_query_post_parent__in() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_public_child_last_id
+	 */
+	public function test_bbp_get_public_child_last_id() {
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$last_id = bbp_get_public_child_last_id( $f, bbp_get_topic_post_type() );
+		$this->assertSame( $t, $last_id );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$last_id = bbp_get_public_child_last_id( $t, bbp_get_reply_post_type() );
+		$this->assertSame( $r, $last_id );
+	}
+
+	/**
+	 * @group  counts
+	 * @covers ::bbp_get_public_child_count
+	 */
+	public function test_bbp_get_public_child_count() {
+
+		/* Empty Forum ********************************************************/
+
+		$f = $this->factory->forum->create();
+
+		// Test initial zero forum public child counts
+		$count = bbp_get_public_child_count( $f, bbp_get_forum_post_type() );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_public_child_count( $f, bbp_get_topic_post_type() );
+		$this->assertSame( 0, $count );
+
+		/* Sub-Forums *********************************************************/
+
+		// 3 public sub-forums
+		$this->factory->forum->create_many( 3, array(
+			'post_parent' => $f,
+		) );
+
+		// 1 private sub-forum
+		$this->factory->forum->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_private_status_id(),
+		) );
+
+		$count = bbp_get_public_child_count( $f, bbp_get_forum_post_type() );
+		$this->assertSame( 3, $count );
+
+		$this->factory->forum->create_many( 2, array(
+			'post_parent' => $f,
+		) );
+
+		$count = bbp_get_public_child_count( $f, bbp_get_forum_post_type() );
+		$this->assertSame( 5, $count );
+
+		/* Topics *************************************************************/
+
+		$t1 = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_spam_status_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = bbp_get_public_child_count( $f, bbp_get_topic_post_type() );
+		$this->assertSame( 3, $count );
+
+		$this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = bbp_get_public_child_count( $f, bbp_get_topic_post_type() );
+		$this->assertSame( 5, $count );
+
+		/* Replies ************************************************************/
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t1[0],
+			'post_status' => bbp_get_spam_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = bbp_get_public_child_count( $t1[0], bbp_get_reply_post_type() );
+		$this->assertSame( 3, $count );
+
+		$this->factory->reply->create_many( 2, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = bbp_get_public_child_count( $t1[0], bbp_get_reply_post_type() );
+		$this->assertSame( 5, $count );
+	}
+
+	/**
+	 * @covers ::bbp_get_public_child_ids
+	 */
+	public function test_bbp_get_public_child_ids() {
+		$f = $this->factory->forum->create();
+
+		// Test initial forum public child counts
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 0, $count );
+
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 0, $count );
+
+		/* Sub-Forums *********************************************************/
+
+		$this->factory->forum->create_many( 3, array(
+			'post_parent' => $f,
+		) );
+
+		$this->factory->forum->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_private_status_id(),
+		) );
+
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 3, $count );
+
+		$this->factory->forum->create_many( 2, array(
+			'post_parent' => $f,
+		) );
+
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 5, $count );
+
+		/* Topics *************************************************************/
+
+		$t1 = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_spam_status_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 3, $count );
+
+		$this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = count( bbp_get_public_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 5, $count );
+
+		/* Replies ************************************************************/
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t1[0],
+			'post_status' => bbp_get_spam_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = count( bbp_get_public_child_ids( $t1[0], bbp_get_reply_post_type() ) );
+		$this->assertSame( 3, $count );
+
+		$this->factory->reply->create_many( 2, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = count( bbp_get_public_child_ids( $t1[0], bbp_get_reply_post_type() ) );
+		$this->assertSame( 5, $count );
+	}
+
+	/**
+	 * @covers ::bbp_get_all_child_ids
+	 */
+	public function test_bbp_get_all_child_ids() {
+		$f = $this->factory->forum->create();
+
+		// Test initial forum public child counts
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 0, $count );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 0, $count );
+
+		/* Sub-Forums *********************************************************/
+
+		$this->factory->forum->create_many( 3, array(
+			'post_parent' => $f,
+		) );
+
+		$this->factory->forum->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_private_status_id(),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 4, $count );
+
+		$this->factory->forum->create_many( 2, array(
+			'post_parent' => $f,
+		) );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_forum_post_type() ) );
+		$this->assertSame( 6, $count );
+
+		/* Topics *************************************************************/
+
+		$t1 = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_spam_status_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 4, $count );
+
+		$this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 6, $count );
+
+		$this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_pending_status_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $f, bbp_get_topic_post_type() ) );
+		$this->assertSame( 7, $count );
+
+		/* Replies ************************************************************/
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t1[0],
+			'post_status' => bbp_get_spam_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $t1[0], bbp_get_reply_post_type() ) );
+		$this->assertSame( 4, $count );
+
+		$this->factory->reply->create_many( 2, array(
+			'post_parent' => $t1[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $t1[0], bbp_get_reply_post_type() ) );
+		$this->assertSame( 6, $count );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t1[0],
+			'post_status' => bbp_get_pending_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t1[0],
+			),
+		) );
+
+		$count = count( bbp_get_all_child_ids( $t1[0], bbp_get_reply_post_type() ) );
+		$this->assertSame( 7, $count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/common/verify-nonce.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/common/verify-nonce.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/common/verify-nonce.php	(revision 7162)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @group common
+ * @group functions
+ * @group bbp_verify_nonce_request
+ */
+class BBP_Tests_Common_Functions_BBPVerifyNonceRequest extends BBP_UnitTestCase {
+	private $http_host   = '';
+	private $server_port = '';
+	private $request_uri = '';
+
+	public function setUp() {
+		parent::setUp();
+
+		if ( isset( $_SERVER['HTTP_HOST'] ) ) {
+			$this->http_host = $_SERVER['HTTP_HOST'];
+		}
+
+		if ( isset( $_SERVER['SERVER_PORT'] ) ) {
+			$this->server_port = $_SERVER['SERVER_PORT'];
+		}
+
+		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+			$this->request_uri = $_SERVER['REQUEST_URI'];
+		}
+	}
+
+	public function tearDown() {
+			$_SERVER['HTTP_HOST'] = $this->http_host;
+
+			$_SERVER['SERVER_PORT'] = $this->server_port;
+
+			$_SERVER['REQUEST_URI'] = $this->request_uri;
+
+
+		parent::tearDown();
+	}
+
+	public function test_bbp_verify_nonce_request_with_port_in_home_url_and_wordpress_installed_in_subdirectory() {
+
+		// fake various $_SERVER parameters
+		$host                   = explode( ':', $_SERVER['HTTP_HOST'] );
+		$_SERVER['HTTP_HOST']   = $host[0] . ':80';
+		$_SERVER['SERVER_PORT'] = 80;
+		$_SERVER['REQUEST_URI'] = '/wordpress/';
+
+		// add port number and subdirecotry to home URL for testing
+		add_filter( 'home_url', array( $this, 'add_port_and_subdirectory_to_home_url' ), 10, 3 );
+
+		// test bbp_verify_nonce_request()
+		$action              = 'verify-this';
+		$_REQUEST[ $action ] = wp_create_nonce( $action );
+		$test                = bbp_verify_nonce_request( $action, $action );
+
+		// clean up!
+		remove_filter( 'home_url', array( $this, 'add_port_and_subdirectory_to_home_url' ), 10, 3 );
+		unset( $_REQUEST[ $action ] );
+
+		// assert!
+		$this->assertSame( 1, $test );
+	}
+
+	/**
+	 * Add port 80 and /wordpress/ subdirectory to home URL.
+	 *
+	 * @param string      $url    The complete home URL including scheme and path.
+	 * @param string      $path   Path relative to the home URL. Blank string if no path is specified.
+	 * @param string|null $scheme Scheme to give the home URL context. Accepts 'http', 'https', 'relative' or null.
+	 * @return string
+	 */
+	public function add_port_and_subdirectory_to_home_url( $url, $path, $scheme ) {
+		$home      = parse_url( get_option( 'home' ) );
+		$home_path = isset( $home['path'] )
+			? $home['path']
+			: '';
+
+		return $scheme . '://' . $home['host'] . ':80' . $home_path . '/wordpress' . $path;
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/core/abstraction.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/core/abstraction.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/core/abstraction.php	(revision 7162)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Tests for core abstraction function wrappers
+ *
+ * @group core
+ * @group abstraction
+ */
+
+class BBP_Tests_Core_Abstraction extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_global_object
+	 * @todo   Implement test_bbp_get_global_object().
+	 */
+	public function test_bbp_get_global_object() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_db
+	 * @todo   Implement test_bbp_db().
+	 */
+	public function test_bbp_db() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_rewrite
+	 * @todo   Implement test_bbp_rewrite().
+	 */
+	public function test_bbp_rewrite() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_root_url
+	 * @todo   Implement test_bbp_get_root_url().
+	 */
+	public function test_bbp_get_root_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_paged_slug
+	 * @todo   Implement test_bbp_get_paged_slug().
+	 */
+	public function test_bbp_get_paged_slug() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_use_pretty_urls
+	 * @todo   Implement test_bbp_use_pretty_urls().
+	 */
+	public function test_bbp_use_pretty_urls() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/core/cache.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/core/cache.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/core/cache.php	(revision 7162)
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Tests for the cache functions.
+ *
+ * @group cache
+ */
+class BBP_Core_Cache_Tests extends BBP_UnitTestCase {
+
+	/**
+	 * @group counts
+	 * @covers ::bbp_clean_post_cache
+	 */
+	public function test_bbp_clean_post_cache() {
+
+		// Get the post types.
+		$tpt = bbp_get_topic_post_type();
+		$rpt = bbp_get_reply_post_type();
+
+		// Set up a forum with 1 topic and 1 reply to that topic.
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Make sure we've cached some data.
+		bbp_get_all_child_ids( $f, $tpt );
+		bbp_get_all_child_ids( $t, $rpt );
+
+		// Setup
+		$f_key        = md5( serialize( array( 'parent_id' => $f, 'post_type' => $tpt ) ) );
+		$t_key        = md5( serialize( array( 'parent_id' => $t, 'post_type' => $rpt ) ) );
+		$last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
+
+		// Keys
+		$f_key = "bbp_child_ids:{$f_key}:{$last_changed}";
+		$t_key = "bbp_child_ids:{$t_key}:{$last_changed}";
+
+		$this->assertEquals( array( $t ), wp_cache_get( $f_key, 'bbpress_posts' ) );
+		$this->assertEquals( array( $r ), wp_cache_get( $t_key, 'bbpress_posts' ) );
+
+		// Clean the reply cache.
+		clean_post_cache( $r );
+
+		// Setup
+		$last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
+
+		// Keys
+		$f_key = "bbp_child_ids:{$f_key}:{$last_changed}";
+		$t_key = "bbp_child_ids:{$t_key}:{$last_changed}";
+
+		$this->assertEquals( false, wp_cache_get( $f_key, 'bbpress_posts' ) );
+		$this->assertEquals( false, wp_cache_get( $t_key, 'bbpress_posts' ) );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/core/functions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/core/functions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/core/functions.php	(revision 7162)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Tests to test that that testing framework is testing tests. Meta, huh?
+ *
+ * @package wordpress-plugins-tests
+ */
+class BBP_bbPress_Tests extends WP_UnitTestCase  {
+
+	/**
+	 * Ensure that bbPress function exists
+	 */
+	function test_bbpress_exists() {
+		$this->assertTrue( function_exists( 'bbpress' ) );
+	}
+
+	/**
+	 * Ensure that bbPress has been installed and activated.
+	 */
+	function test_plugin_activated() {
+		$this->assertTrue( is_plugin_active( 'bbpress/bbpress.php' ) );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/core/update.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/core/update.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/core/update.php	(revision 7162)
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Tests for the core update functions.
+ *
+ * @group core
+ * @group update
+ */
+class BBP_Tests_Core_Update extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_is_install
+	 * @todo   Implement test_bbp_is_install().
+	 */
+	public function test_bbp_is_install() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_update
+	 * @todo   Implement test_bbp_is_update().
+	 */
+	public function test_bbp_is_update() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_activation
+	 * @todo   Implement test_bbp_is_activation().
+	 */
+	public function test_bbp_is_activation() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_deactivation
+	 * @todo   Implement test_bbp_is_deactivation().
+	 */
+	public function test_bbp_is_deactivation() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_version_bump
+	 * @todo   Implement test_bbp_version_bump().
+	 */
+	public function test_bbp_version_bump() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_setup_updater
+	 * @todo   Implement test_bbp_setup_updater().
+	 */
+	public function test_bbp_setup_updater() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @group canonical
+	 * @covers ::bbp_create_initial_content
+	 */
+	public function test_bbp_create_initial_content() {
+
+		$category_id = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$u = $this->factory->user->create();
+
+		bbp_create_initial_content( array(
+			'forum_parent' => $category_id,
+			'forum_author' => $u,
+			'topic_author' => $u,
+			'reply_author' => $u
+		) );
+
+		$forum_id = bbp_forum_query_subforum_ids( $category_id );
+		$forum_id = (int) $forum_id[0];
+		$topic_id = bbp_get_forum_last_topic_id( $forum_id );
+		$reply_id = bbp_get_forum_last_reply_id( $forum_id );
+
+		// Forum post
+		$this->assertSame( 'General', bbp_get_forum_title( $forum_id ) );
+		$this->assertSame( 'General Discussion', bbp_get_forum_content( $forum_id ) );
+		$this->assertSame( 'open', bbp_get_forum_status( $forum_id ) );
+		$this->assertTrue( bbp_is_forum_public( $forum_id ) );
+		$this->assertSame( $category_id, bbp_get_forum_parent_id( $forum_id ) );
+
+		// Topic post
+		$this->assertSame( $forum_id, bbp_get_topic_forum_id( $topic_id ) );
+		$this->assertSame( 'Hello World!', bbp_get_topic_title( $topic_id ) );
+		remove_all_filters( 'bbp_get_topic_content' );
+		$topic_content = "This is the very first topic in these forums.";
+		$this->assertSame( $topic_content, bbp_get_topic_content( $topic_id ) );
+		$this->assertSame( 'publish', bbp_get_topic_status( $topic_id ) );
+		$this->assertTrue( bbp_is_topic_published( $topic_id ) );
+
+		// Reply post
+		$this->assertSame( $forum_id, bbp_get_reply_forum_id( $reply_id ) );
+		$this->assertSame( 'Reply To: Hello World!', bbp_get_reply_title( $reply_id ) );
+		$this->assertSame( $reply_id, bbp_get_reply_title_fallback( $reply_id ) );
+		remove_all_filters( 'bbp_get_reply_content' );
+		$reply_content = "And this is the very first reply.";
+		$this->assertSame( $reply_content, bbp_get_reply_content( $reply_id ) );
+		$this->assertSame( 'publish', bbp_get_reply_status( $reply_id ) );
+		$this->assertTrue( bbp_is_reply_published( $reply_id ) );
+
+		// Category meta
+		$this->assertSame( 1, bbp_get_forum_subforum_count( $category_id, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count( $category_id, false, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count_hidden( $category_id, true ) );
+		$this->assertSame( 0, bbp_get_forum_reply_count( $category_id, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $category_id, true, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $category_id, true, true ) );
+		$this->assertSame( 0, bbp_get_forum_post_count( $category_id, false, true ) );
+		$this->assertSame( 2, bbp_get_forum_post_count( $category_id, true, true ) );
+		$this->assertSame( $topic_id, bbp_get_forum_last_topic_id( $category_id ) );
+		$this->assertSame( 'Hello World!', bbp_get_forum_last_topic_title( $category_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_reply_id( $category_id ) );
+		$this->assertSame( 'Reply To: Hello World!', bbp_get_forum_last_reply_title( $category_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_active_id( $category_id ) );
+		$this->assertSame( '1 day, 16 hours ago', bbp_get_forum_last_active_time( $category_id ) );
+
+		// Forum meta
+		$this->assertSame( 0, bbp_get_forum_subforum_count( $forum_id, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $forum_id, false, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count_hidden( $forum_id, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $forum_id, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $forum_id, true, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $forum_id, true, true ) );
+		$this->assertSame( 2, bbp_get_forum_post_count( $forum_id, false, true ) );
+		$this->assertSame( 2, bbp_get_forum_post_count( $forum_id, true, true ) );
+		$this->assertSame( $topic_id, bbp_get_forum_last_topic_id( $forum_id ) );
+		$this->assertSame( 'Hello World!', bbp_get_forum_last_topic_title( $forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_reply_id( $forum_id ) );
+		$this->assertSame( 'Reply To: Hello World!', bbp_get_forum_last_reply_title( $forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_active_id( $forum_id ) );
+		$this->assertSame( '1 day, 16 hours ago', bbp_get_forum_last_active_time( $forum_id ) );
+
+		// Topic meta
+		$this->assertSame( '127.0.0.1', bbp_current_author_ip( $topic_id ) );
+		$this->assertSame( $forum_id, bbp_get_topic_forum_id( $topic_id ) );
+		$this->assertSame( 1, bbp_get_topic_voice_count( $topic_id, true ) );
+		$this->assertSame( 1, bbp_get_topic_reply_count( $topic_id, true ) );
+		$this->assertSame( 0, bbp_get_topic_reply_count_hidden( $topic_id, true ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_reply_id( $topic_id ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_active_id( $topic_id ) );
+		$this->assertSame( '1 day, 16 hours ago', bbp_get_topic_last_active_time( $topic_id ) );
+
+		// Reply Meta
+		$this->assertSame( '127.0.0.1', bbp_current_author_ip( $reply_id ) );
+		$this->assertSame( $forum_id, bbp_get_reply_forum_id( $reply_id ) );
+		$this->assertSame( $topic_id, bbp_get_reply_topic_id( $reply_id ) );
+	}
+
+	/**
+	 * @covers ::bbp_version_updater
+	 * @todo   Implement test_bbp_version_updater().
+	 */
+	public function test_bbp_version_updater() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_add_activation_redirect
+	 * @todo   Implement test_bbp_add_activation_redirect().
+	 */
+	public function test_bbp_add_activation_redirect() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_make_current_user_keymaster
+	 * @todo   Implement test_bbp_make_current_user_keymaster().
+	 */
+	public function test_bbp_make_current_user_keymaster() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/extend/buddypress/activity.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/extend/buddypress/activity.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/extend/buddypress/activity.php	(revision 7162)
@@ -0,0 +1,266 @@
+<?php
+
+/**
+ * BuddyPress Extension Activity Tests.
+ *
+ * @group extend
+ * @group buddypress
+ * @group activity
+ */
+class BBP_Tests_Extend_BuddyPress_Activity extends BBP_UnitTestCase {
+
+	/**
+	 * Copied from `BBP_Forums_Group_Extension::new_forum()`.
+	 *
+	 * @since x.x.x
+	 *
+	 * @param int $forum_id The forum id.
+	 * @param int $group_id The group id.
+	 */
+	private function attach_forum_to_group( $forum_id, $group_id ) {
+		bbp_add_forum_id_to_group( $group_id, $forum_id );
+		bbp_add_group_id_to_forum( $forum_id, $group_id );
+	}
+
+	/**
+	 * Dynamic activity actions for site-wide forum topics.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @ticket BBP2794
+	 */
+	public function test_bp_activity_actions_for_site_wide_forum_topic() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+		) );
+
+		// Set up our activity text test string.
+		$user_link       = bbp_get_user_profile_link( $u );
+		$topic_permalink = bbp_get_topic_permalink( $t );
+		$topic_title     = get_post_field( 'post_title',   $t, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+		$forum_permalink = bbp_get_forum_permalink( $f );
+		$forum_title     = get_post_field( 'post_title', $f, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+		$activity_text   = sprintf( esc_html__( '%1$s started the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		// Create the activity.
+		bbpress()->extend->buddypress->activity->topic_create( $t, $f, array(), $u );
+
+		$activity_id = (int) get_post_meta( $t, '_bbp_activity_id', true );
+		$activity    = new BP_Activity_Activity( $activity_id );
+
+		// Test the default generated string.
+		$this->assertEquals( $activity_text, $activity->action );
+
+		// Update a few items for testing.
+		wp_update_user( array( 'ID' => $u, 'display_name' => 'New Name' ) );
+		$user_link = bbp_get_user_profile_link( $u );
+
+		wp_update_post( array( 'ID' => $f, 'post_title' => 'New Forum Title' ) );
+		$forum_link = '<a href="' . $forum_permalink . '">New Forum Title</a>';
+
+		wp_update_post( array( 'ID' => $t, 'post_title' => 'New Topic Title' ) );
+		$topic_link = '<a href="' . $topic_permalink . '">New Topic Title</a>';
+
+		// Set up our new test string.
+		$activity_text = sprintf( esc_html__( '%1$s started the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		// Are we dynamic?
+		$this->assertEquals( $activity_text, $activity->action );
+	}
+
+	/**
+	 * Dynamic activity actions for replies to site-wide forum topics.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @ticket BBP2794
+	 */
+	public function test_bp_activity_actions_for_reply_to_site_wide_forum_topic() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+		) );
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+		) );
+
+		// Set up our activity text test string.
+		$user_link       = bbp_get_user_profile_link( $u );
+		$topic_permalink = bbp_get_topic_permalink( $t );
+		$topic_title     = get_post_field( 'post_title',   $t, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+		$forum_permalink = bbp_get_forum_permalink( $f );
+		$forum_title     = get_post_field( 'post_title', $f, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+		$activity_text   = sprintf( esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		// Create the activity.
+		bbpress()->extend->buddypress->activity->reply_create( $r, $t, $f, array(), $u );
+
+		$activity_id = (int) get_post_meta( $r, '_bbp_activity_id', true );
+		$activity    = new BP_Activity_Activity( $activity_id );
+
+		// Test the default generated string.
+		$this->assertEquals( $activity_text, $activity->action );
+
+		// Update a few items for testing.
+		wp_update_user( array( 'ID' => $u, 'display_name' => 'New Name' ) );
+		$user_link = bbp_get_user_profile_link( $u );
+
+		wp_update_post( array( 'ID' => $f, 'post_title' => 'New Forum Title' ) );
+		$forum_link = '<a href="' . $forum_permalink . '">New Forum Title</a>';
+
+		wp_update_post( array( 'ID' => $t, 'post_title' => 'New Topic Title' ) );
+		$topic_link = '<a href="' . $topic_permalink . '">New Topic Title</a>';
+
+		// Set up our new test string.
+		$activity_text = sprintf( esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		// Are we dynamic?
+		$this->assertEquals( $activity_text, $activity->action );
+	}
+
+	/**
+	 * Dynamic activity actions for group forum topics.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @ticket BBP2794
+	 */
+	public function test_bp_activity_actions_for_group_forum_topic() {
+
+		// See https://bbpress.trac.wordpress.org/ticket/2794.
+		// See https://bbpress.trac.wordpress.org/ticket/3089.
+		$this->markTestSkipped( 'Skipping dynamic group activity action tests.' );
+
+		$g = $this->bp_factory->group->create();
+		$group = groups_get_group( array( 'group_id' => $g ) );
+		$u = $group->creator_id;
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+		) );
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+		) );
+		$this->attach_forum_to_group( $f, $g );
+		buddypress()->groups->current_group = $group;
+
+		// Set up our activity text test string.
+		$user_link       = bbp_get_user_profile_link( $u );
+		$topic_permalink = bbp_get_topic_permalink( $t );
+		$topic_title     = get_post_field( 'post_title', $t, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+		$forum_permalink = bbp_get_forum_permalink( $f );
+		$forum_title     = get_post_field( 'post_title', $f, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+		$activity_text   = sprintf( esc_html__( '%1$s started the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		// Create the activity.
+		bbpress()->extend->buddypress->activity->topic_create( $t, $f, array(), $u );
+
+		$activity_id = (int) get_post_meta( $t, '_bbp_activity_id', true );
+		$activity    = new BP_Activity_Activity( $activity_id );
+
+		// Test the default generated string.
+		$this->assertEquals( $activity_text, $activity->action );
+
+		// Update a few items for testing.
+		wp_update_user( array( 'ID' => $u, 'display_name' => 'New Name' ) );
+		$user_link = bbp_get_user_profile_link( $u );
+
+		wp_update_post( array( 'ID' => $f, 'post_title' => 'New Forum Title' ) );
+		$forum_link = '<a href="' . $forum_permalink . '">New Forum Title</a>';
+
+		wp_update_post( array( 'ID' => $t, 'post_title' => 'New Topic Title' ) );
+		$topic_link = '<a href="' . $topic_permalink . '">New Topic Title</a>';
+
+		// Set up our new test string.
+		$activity_text = sprintf( esc_html__( '%1$s started the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		// Are we dynamic?
+		$this->assertEquals( $activity_text, $activity->action );
+	}
+
+	/**
+	 * Dynamic activity actions for replies to group forum topics.
+	 *
+	 * @since 2.6.0 bbPress (r6370)
+	 *
+	 * @ticket BBP2794
+	 */
+	public function test_bp_activity_actions_for_reply_to_group_forum_topic() {
+
+		// See https://bbpress.trac.wordpress.org/ticket/2794.
+		// See https://bbpress.trac.wordpress.org/ticket/3089.
+		$this->markTestSkipped( 'Skipping dynamic group activity action tests.' );
+
+		$g = $this->bp_factory->group->create();
+		$group = groups_get_group( array( 'group_id' => $g ) );
+		$u = $group->creator_id;
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+		) );
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+		) );
+		$this->attach_forum_to_group( $f, $g );
+		buddypress()->groups->current_group = $group;
+
+		// Set up our activity text test string.
+		$user_link       = bbp_get_user_profile_link( $u );
+		$topic_permalink = bbp_get_topic_permalink( $t );
+		$topic_title     = get_post_field( 'post_title',   $t, 'raw' );
+		$topic_link      = '<a href="' . $topic_permalink . '">' . $topic_title . '</a>';
+		$forum_permalink = bbp_get_forum_permalink( $f );
+		$forum_title     = get_post_field( 'post_title', $f, 'raw' );
+		$forum_link      = '<a href="' . $forum_permalink . '">' . $forum_title . '</a>';
+		$activity_text   = sprintf( esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		// Create the activity.
+		bbpress()->extend->buddypress->activity->reply_create( $r, $t, $f, array(), $u );
+
+		$activity_id = (int) get_post_meta( $r, '_bbp_activity_id', true );
+		$activity    = new BP_Activity_Activity( $activity_id );
+
+		// Test the default generated string.
+		$this->assertEquals( $activity_text, $activity->action );
+
+		// Update a few items for testing.
+		wp_update_user( array( 'ID' => $u, 'display_name' => 'New Name' ) );
+		$user_link = bbp_get_user_profile_link( $u );
+
+		wp_update_post( array( 'ID' => $f, 'post_title' => 'New Forum Title' ) );
+		$forum_link = '<a href="' . $forum_permalink . '">New Forum Title</a>';
+
+		wp_update_post( array( 'ID' => $t, 'post_title' => 'New Topic Title' ) );
+		$topic_link = '<a href="' . $topic_permalink . '">New Topic Title</a>';
+
+		// Set up our new test string.
+		$activity_text = sprintf( esc_html__( '%1$s replied to the topic %2$s in the forum %3$s', 'bbpress' ), $user_link, $topic_link, $forum_link );
+
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		// Are we dynamic?
+		$this->assertEquals( $activity_text, $activity->action );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/counts.php	(revision 7162)
@@ -0,0 +1,611 @@
+<?php
+
+/**
+ * Tests for the forum component count functions.
+ *
+ * @group forums
+ * @group functions
+ * @group counts
+ */
+class BBP_Tests_Forums_Functions_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * Generic function to test the forum counts with a new topic
+	 */
+	public function test_bbp_forum_new_topic_counts() {
+		remove_action( 'bbp_insert_topic', 'bbp_insert_topic_update_counts', 10 );
+
+		$f = $this->factory->forum->create();
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => bbp_get_current_user_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$u = $this->factory->user->create();
+
+		// Don't attempt to send an email. This is for speed and PHP errors.
+		remove_action( 'bbp_new_topic', 'bbp_notify_forum_subscribers', 11, 4 );
+
+		// Simulate the 'bbp_new_topic' action.
+		do_action( 'bbp_new_topic', $t1, $f, false, bbp_get_current_user_id(), $t1 );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Simulate the 'bbp_new_topic' action.
+		do_action( 'bbp_new_topic', $t2, $f, false, $u , $t2 );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		// Re-add removed actions.
+		add_action( 'bbp_insert_topic', 'bbp_insert_topic_update_counts', 10, 2 );
+		add_action( 'bbp_new_topic',    'bbp_notify_forum_subscribers',   11, 4 );
+	}
+
+	/**
+	 * Generic function to test the forum counts on a trashed/untrashed topic
+	 */
+	public function test_bbp_forum_trashed_untrashed_topic_counts() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create_many( 1, array(
+			'post_parent' => $t[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[0],
+			),
+		) );
+		$r2 = $this->factory->reply->create_many( 1, array(
+			'post_parent' => $t[1],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[1],
+			),
+		) );
+
+		$count = bbp_update_forum_topic_count( $f );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_update_forum_topic_count_hidden( $f );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_forum_reply_count( $f );
+		$this->assertSame( 2, $count );
+
+		// ToDo: Update this to use bbp_trash_topic().
+		wp_trash_post( $t[1] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		// ToDo: Update this to use bbp_untrash_topic().
+		wp_untrash_post( $t[1] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * Generic function to test the forum counts on a spammed/unspammed topic
+	 */
+	public function test_bbp_forum_spammed_unspammed_topic_counts() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create_many( 1, array(
+			'post_parent' => $t[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[0],
+			),
+		) );
+		$r2 = $this->factory->reply->create_many( 1, array(
+			'post_parent' => $t[1],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[1],
+			),
+		) );
+
+		$count = bbp_update_forum_topic_count( $f );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_update_forum_topic_count_hidden( $f );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_forum_reply_count( $f );
+		$this->assertSame( 2, $count );
+
+		bbp_spam_topic( $t[1] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		bbp_unspam_topic( $t[1] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * Generic function to test the forum counts on a approved/unapproved topic
+	 */
+	public function test_bbp_forum_approved_unapproved_topic_counts() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t[1],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[1],
+			),
+		) );
+		$r2 = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t[2],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t[2],
+			),
+		) );
+
+		$count = bbp_update_forum_topic_count( $f );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_update_forum_topic_count_hidden( $f );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_forum_reply_count( $f );
+		$this->assertSame( 4, $count );
+
+		bbp_unapprove_topic( $t[2] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 2, $count );
+
+		bbp_approve_topic( $t[2] );
+
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( 4, $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_forum_topic_count
+	 */
+	public function test_bbp_bump_forum_topic_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_bump_forum_topic_count( $f );
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_forum_topic_count
+	 */
+	public function test_bbp_increase_forum_topic_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_increase_forum_topic_count( $f );
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_forum_topic_count
+	 */
+	public function test_bbp_decrease_forum_topic_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '0', $count );
+
+		$t = $this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+		) );
+
+		bbp_update_forum_topic_count( $f );
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '2', $count );
+
+		bbp_update_forum_topic_count( $f );
+
+		bbp_decrease_forum_topic_count( $f );
+
+		$count = bbp_get_forum_topic_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_forum_topic_count_hidden
+	 */
+	public function test_bbp_bump_forum_topic_count_hidden() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_bump_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_forum_topic_count_hidden
+	 */
+	public function test_bbp_increase_forum_topic_count_hidden() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_increase_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_forum_topic_count_hidden
+	 */
+	public function test_bbp_decrease_forum_topic_count_hidden() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '0', $count );
+
+		$t = $this->factory->topic->create_many( 2, array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_spam_status_id(),
+			'topic_meta' => array(
+				'forum_id' => $f,
+				'spam_meta_status' => 'publish',
+			)
+		) );
+
+		bbp_update_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '2', $count );
+
+		bbp_decrease_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_forum_reply_count
+	 */
+	public function test_bbp_bump_forum_reply_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_bump_forum_reply_count( $f );
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_forum_reply_count
+	 */
+	public function test_bbp_increase_forum_reply_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '0', $count );
+
+		bbp_increase_forum_reply_count( $f );
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_forum_reply_count
+	 */
+	public function test_bbp_decrease_forum_reply_count() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '0', $count );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+		) );
+
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+		) );
+
+		bbp_update_forum_reply_count( $f );
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '2', $count );
+
+		bbp_decrease_forum_reply_count( $f );
+
+		$count = bbp_get_forum_reply_count( $f );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_subforum_count
+	 */
+	public function test_bbp_update_forum_subforum_count() {
+		$f1 = $this->factory->forum->create();
+
+		$count = bbp_get_forum_subforum_count( $f1, true );
+		$this->assertSame( 0, $count );
+
+		$f2 = $this->factory->forum->create_many( 3, array(
+			'post_parent' => $f1,
+		) );
+
+		$count = bbp_get_forum_subforum_count( $f1, true );
+		$this->assertSame( 3, $count );
+
+		bbp_update_forum_subforum_count( $f1, 10 );
+
+		$count = bbp_get_forum_subforum_count( $f1, true );
+		$this->assertSame( 10, $count );
+
+		bbp_update_forum_subforum_count( $f1 );
+
+		$count = bbp_get_forum_subforum_count( $f1, true );
+		$this->assertSame( 3, $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_topic_count
+	 */
+	public function test_bbp_update_forum_topic_count() {
+		// Create a top level forum f1
+		$f1 = $this->factory->forum->create();
+
+		bbp_normalize_forum( $f1 );
+
+		$count = bbp_get_forum_topic_count( $f1 );
+		$this->assertSame( '0', $count );
+
+		// Create 3 topics in f1
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f1,
+		) );
+
+		bbp_update_forum_topic_count( $f1 );
+
+		$count = bbp_get_forum_topic_count( $f1 );
+		$this->assertSame( '3', $count );
+
+		// Create a new sub forum of f1
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		// Create another sub forum of f1
+		$f3 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		bbp_update_forum_topic_count( $f1 );
+		bbp_update_forum_topic_count( $f2 );
+		bbp_update_forum_topic_count( $f3 );
+
+		$count = bbp_get_forum_topic_count( $f1 );
+		$this->assertSame( '3', $count );
+
+		$count = bbp_get_forum_topic_count( $f2 );
+		$this->assertSame( '0', $count );
+
+		$count = bbp_get_forum_topic_count( $f3 );
+		$this->assertSame( '0', $count );
+
+		// Create some topics in forum f2
+		$this->factory->topic->create_many( 4, array(
+			'post_parent' => $f2,
+		) );
+
+		bbp_update_forum_topic_count( $f1 );
+		bbp_update_forum_topic_count( $f2 );
+		bbp_update_forum_topic_count( $f3 );
+
+		$count = bbp_get_forum_topic_count( $f1 );
+		$this->assertSame( '7', $count );
+
+		$count = bbp_get_forum_topic_count( $f2 );
+		$this->assertSame( '4', $count );
+
+		$count = bbp_get_forum_topic_count( $f3 );
+		$this->assertSame( '0', $count );
+
+		// Create some topics in forum f3
+		$this->factory->topic->create_many( 5, array(
+			'post_parent' => $f3,
+		) );
+
+		bbp_update_forum_topic_count( $f1 );
+		bbp_update_forum_topic_count( $f2 );
+		bbp_update_forum_topic_count( $f3 );
+
+		$count = bbp_get_forum_topic_count( $f1 );
+		$this->assertSame( '12', $count );
+
+		$count = bbp_get_forum_topic_count( $f2 );
+		$this->assertSame( '4', $count );
+
+		$count = bbp_get_forum_topic_count( $f3 );
+		$this->assertSame( '5', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_topic_count_hidden
+	 */
+	public function test_bbp_update_forum_topic_count_hidden() {
+		$f = $this->factory->forum->create();
+
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 0, $count );;
+
+		bbp_spam_topic( $t[2] );
+
+		bbp_update_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 1, $count );;
+
+		bbp_unapprove_topic( $t[0] );
+
+		bbp_update_forum_topic_count_hidden( $f );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_reply_count
+	 */
+	public function test_bbp_update_forum_reply_count() {
+		$f1 = $this->factory->forum->create();
+
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f1,
+			'topic_meta' => array(
+				'forum_id' => $f1,
+			),
+		) );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f2,
+			'topic_meta' => array(
+				'forum_id' => $f2,
+			),
+		) );
+
+		$count = bbp_get_forum_reply_count( $f1, false, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_forum_reply_count( $f1 );
+		$this->assertSame( 0, $count );
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t1,
+			'reply_meta' => array(
+				'forum_id' => $f1,
+				'topic_id' => $t1,
+			),
+		) );
+
+		$count = bbp_update_forum_reply_count( $f1 );
+		$this->assertSame( 3, $count );
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t2,
+			'reply_meta' => array(
+				'forum_id' => $f2,
+				'topic_id' => $t2,
+			),
+		) );
+
+		$count = bbp_update_forum_reply_count( $f1 );
+		$this->assertSame( 6, $count );
+
+		$count = bbp_update_forum_reply_count( $f2 );
+		$this->assertSame( 3, $count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/forum.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/forum.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/forum.php	(revision 7162)
@@ -0,0 +1,286 @@
+<?php
+
+/**
+ * Tests for bbPress forum functions.
+ *
+ * @group forums
+ * @group functions
+ * @group forum
+ */
+class BBP_Tests_Forums_Functions_Forum extends BBP_UnitTestCase {
+
+	/**
+	 * @group canonical
+	 * @covers ::bbp_insert_forum
+	 */
+	public function test_bbp_insert_forum() {
+
+		$c = $this->factory->forum->create( array(
+			'post_title' => 'Category 1',
+			'post_content' => 'Content of Category 1',
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Forum 1',
+			'post_content' => 'Content of Forum 1',
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the category.
+		$category = bbp_get_forum( $c );
+
+		// Get the forum.
+		$forum = bbp_get_forum( $f );
+
+		// Category post.
+		$this->assertSame( 'Category 1', bbp_get_forum_title( $c ) );
+		$this->assertSame( 'Content of Category 1', bbp_get_forum_content( $c ) );
+		$this->assertSame( 'open', bbp_get_forum_status( $c ) );
+		$this->assertSame( 'category', bbp_get_forum_type( $c ) );
+		$this->assertTrue( bbp_is_forum( $c ) );
+		$this->assertTrue( bbp_is_forum_category( $c ) );
+		$this->assertTrue( bbp_is_forum_open( $c ) );
+		$this->assertTrue( bbp_is_forum_public( $c ) );
+		$this->assertFalse( bbp_is_forum_closed( $c ) );
+		$this->assertFalse( bbp_is_forum_hidden( $c ) );
+		$this->assertFalse( bbp_is_forum_private( $c ) );
+		$this->assertSame( 0, bbp_get_forum_parent_id( $c ) );
+		$this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?forum=' . $category->post_name, $category->guid );
+
+		// Forum post.
+		$this->assertSame( 'Forum 1', bbp_get_forum_title( $f ) );
+		$this->assertSame( 'Content of Forum 1', bbp_get_forum_content( $f ) );
+		$this->assertSame( 'open', bbp_get_forum_status( $f ) );
+		$this->assertSame( 'forum', bbp_get_forum_type( $f ) );
+		$this->assertTrue( bbp_is_forum( $f ) );
+		$this->assertTrue( bbp_is_forum_open( $f ) );
+		$this->assertTrue( bbp_is_forum_public( $f ) );
+		$this->assertFalse( bbp_is_forum_closed( $f ) );
+		$this->assertFalse( bbp_is_forum_hidden( $f ) );
+		$this->assertFalse( bbp_is_forum_private( $f ) );
+		$this->assertSame( $c, bbp_get_forum_parent_id( $f ) );
+		$this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?forum=' . $category->post_name . '/' . $forum->post_name, $forum->guid );
+
+		// Category meta.
+		$this->assertSame( 1, bbp_get_forum_subforum_count( $c, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count( $c, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $c, true, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count_hidden( $c, true ) );
+		$this->assertSame( 0, bbp_get_forum_reply_count( $c, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $c, true, true ) );
+		$this->assertSame( 0, bbp_get_forum_post_count( $c, false, true ) );
+		$this->assertSame( 2, bbp_get_forum_post_count( $c, true, true ) );
+		$this->assertSame( $t, bbp_get_forum_last_topic_id( $c ) );
+		$this->assertSame( $r, bbp_get_forum_last_reply_id( $c ) );
+		$this->assertSame( $r, bbp_get_forum_last_active_id( $c ) );
+		$this->assertSame( '4 days, 4 hours ago', bbp_get_forum_last_active_time( $c ) );
+
+		// Forum meta.
+		$this->assertSame( 0, bbp_get_forum_subforum_count( $f, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $f, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_topic_count( $f, true, true ) );
+		$this->assertSame( 0, bbp_get_forum_topic_count_hidden( $f, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $f, false, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $f, true, true ) );
+
+		$this->assertSame( 2, bbp_get_forum_post_count( $f, false, true ) );
+		$this->assertSame( 2, bbp_get_forum_post_count( $f, true, true ) );
+		$this->assertSame( $t, bbp_get_forum_last_topic_id( $f ) );
+		$this->assertSame( $r, bbp_get_forum_last_reply_id( $f ) );
+		$this->assertSame( $r, bbp_get_forum_last_active_id( $f ) );
+		$this->assertSame( '4 days, 4 hours ago', bbp_get_forum_last_active_time( $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_new_forum_handler
+	 * @todo   Implement test_bbp_new_forum_handler().
+	 */
+	public function test_bbp_new_forum_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_forum_handler
+	 * @todo   Implement test_bbp_edit_forum_handler().
+	 */
+	public function test_bbp_edit_forum_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_save_forum_extras
+	 * @todo   Implement test_bbp_save_forum_extras().
+	 */
+	public function test_bbp_save_forum_extras() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_remove_forum_from_all_subscriptions
+	 * @todo   Implement test_bbp_remove_forum_from_all_subscriptions().
+	 */
+	public function test_bbp_remove_forum_from_all_subscriptions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_forum
+	 * @todo   Implement test_bbp_update_forum().
+	 */
+	public function test_bbp_update_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_forum_edit
+	 * @todo   Implement test_bbp_check_forum_edit().
+	 */
+	public function test_bbp_check_forum_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+	/**
+	 * @covers ::bbp_delete_forum_topics
+	 * @todo   Implement test_bbp_delete_forum_topics().
+	 */
+	public function test_bbp_delete_forum_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trash_forum_topics
+	 * @todo   Implement test_bbp_trash_forum_topics().
+	 */
+	public function test_bbp_trash_forum_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrash_forum_topics
+	 * @todo   Implement test_bbp_untrash_forum_topics().
+	 */
+	public function test_bbp_untrash_forum_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_delete_forum
+	 * @todo   Implement test_bbp_delete_forum().
+	 */
+	public function test_bbp_delete_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trash_forum
+	 * @todo   Implement test_bbp_trash_forum().
+	 */
+	public function test_bbp_trash_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrash_forum
+	 * @todo   Implement test_bbp_untrash_forum().
+	 */
+	public function test_bbp_untrash_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_deleted_forum
+	 * @todo   Implement test_bbp_deleted_forum().
+	 */
+	public function test_bbp_deleted_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trashed_forum
+	 * @todo   Implement test_bbp_trashed_forum().
+	 */
+	public function test_bbp_trashed_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrashed_forum
+	 * @todo   Implement test_bbp_untrashed_forum().
+	 */
+	public function test_bbp_untrashed_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/query.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/query.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/query.php	(revision 7162)
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * Tests for the forum component query functions.
+ *
+ * @group forums
+ * @group functions
+ * @group query
+ */
+class BBP_Tests_Forums_Functions_Query extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_exclude_forum_ids
+	 * @todo   Implement test_bbp_exclude_forum_ids().
+	 */
+	public function test_bbp_exclude_forum_ids() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_query_topic_ids
+	 */
+	public function test_bbp_forum_query_topic_ids() {
+		$f = $this->factory->forum->create();
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$t3 = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->assertEqualSets( array( $t1, $t2, $t3 ), bbp_forum_query_topic_ids( $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_forum_query_subforum_ids
+	 */
+	public function test_bbp_forum_query_subforum_ids() {
+		$f1 = $this->factory->forum->create();
+
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$f3 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$this->assertEqualSets( array( $f2, $f3 ), bbp_forum_query_subforum_ids( $f1 ) );
+	}
+
+	/**
+	 * @covers ::bbp_forum_query_last_reply_id
+	 */
+	public function test_bbp_forum_query_last_reply_id() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply id.
+		$query_last_reply_f = bbp_forum_query_last_reply_id( $f );
+		$this->assertSame( $query_last_reply_f, bbp_get_forum_last_reply_id( $f ) );
+
+		// Get the categories last reply id.
+		$query_last_reply_c = bbp_forum_query_last_reply_id( $c );
+		$this->assertSame( $query_last_reply_c, bbp_get_forum_last_reply_id( $c ) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply id.
+		$query_last_reply_f = bbp_forum_query_last_reply_id( $f );
+		$this->assertSame( $query_last_reply_f, bbp_get_forum_last_reply_id( $f ) );
+
+		// Get the categories last reply id.
+		$query_last_reply_c = bbp_forum_query_last_reply_id( $c );
+		$this->assertSame( $query_last_reply_c, bbp_get_forum_last_reply_id( $c ) );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/status.php	(revision 7162)
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Tests for bbPress forum statuses and types functions.
+ *
+ * @group forums
+ * @group functions
+ * @group status
+ */
+class BBP_Tests_Forums_Functions_Status extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_forum_statuses
+	 * @todo   Implement test_bbp_get_forum_statuses().
+	 */
+	public function test_bbp_get_forum_statuses() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_types
+	 * @todo   Implement test_bbp_get_forum_types().
+	 */
+	public function test_bbp_get_forum_types() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_close_forum
+	 * @todo   Implement test_bbp_close_forum().
+	 */
+	public function test_bbp_close_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_open_forum
+	 * @todo   Implement test_bbp_open_forum().
+	 */
+	public function test_bbp_open_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_categorize_forum
+	 * @todo   Implement test_bbp_categorize_forum().
+	 */
+	public function test_bbp_categorize_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_normalize_forum
+	 * @todo   Implement test_bbp_normalize_forum().
+	 */
+	public function test_bbp_normalize_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_publicize_forum
+	 * @todo   Implement test_bbp_publicize_forum().
+	 */
+	public function test_bbp_publicize_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_privatize_forum
+	 * @todo   Implement test_bbp_privatize_forum().
+	 */
+	public function test_bbp_privatize_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_hide_forum
+	 * @todo   Implement test_bbp_hide_forum().
+	 */
+	public function test_bbp_hide_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/update-last-thing.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/update-last-thing.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/update-last-thing.php	(revision 7162)
@@ -0,0 +1,212 @@
+<?php
+/**
+ * Tests for the `bbp_update_forum_last_*()` functions.
+ *
+ * @group forums
+ * @group functions
+ * @group update_last_thing
+ */
+
+class BBP_Tests_Forums_Functions_Update_Forum_Last_Thing extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_update_forum_last_topic_id
+	 */
+	public function test_bbp_update_forum_last_topic_id() {
+		$f1 = $this->factory->forum->create();
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f1,
+			'topic_meta' => array(
+				'forum_id' => $f1,
+			),
+		) );
+
+		$id = bbp_update_forum_last_topic_id( $f1, $t1 );
+		$this->assertSame( $t1, $id );
+
+		$id = bbp_get_forum_last_topic_id( $f1 );
+		$this->assertSame( $t1, $id );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f2,
+			'topic_meta' => array(
+				'forum_id' => $f2,
+			),
+		) );
+
+		bbp_update_forum_last_topic_id( $f2 );
+		$id = bbp_get_forum_last_topic_id( $f2 );
+		$this->assertSame( $t2, $id );
+
+		bbp_update_forum_last_topic_id( $f1 );
+		$id = bbp_get_forum_last_topic_id( $f1 );
+		$this->assertSame( $t2, $id );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_last_reply_id
+	 */
+	public function test_bbp_update_forum_last_reply_id() {
+		$f1 = $this->factory->forum->create();
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f1,
+			'topic_meta' => array(
+				'forum_id' => $f1,
+			),
+		) );
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f2,
+			'topic_meta' => array(
+				'forum_id' => $f2,
+			),
+		) );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t1,
+			'reply_meta' => array(
+				'forum_id' => $f1,
+				'topic_id' => $t1,
+			),
+		) );
+
+		$id = bbp_update_forum_last_reply_id( $f1, $r1 );
+		$this->assertSame( $r1, $id );
+
+		$id = bbp_get_forum_last_reply_id( $f1 );
+		$this->assertSame( $r1, $id );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t2,
+			'reply_meta' => array(
+				'forum_id' => $f2,
+				'topic_id' => $t2,
+			),
+		) );
+
+		bbp_update_forum_last_reply_id( $f2 );
+		$id = bbp_get_forum_last_reply_id( $f2 );
+		$this->assertSame( $r2, $id );
+
+		bbp_update_forum_last_reply_id( $f1 );
+		$id = bbp_get_forum_last_reply_id( $f1 );
+		$this->assertSame( $r2, $id );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_last_active_id
+	 */
+	public function test_bbp_update_forum_last_active_id() {
+		$f1 = $this->factory->forum->create();
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f1,
+			'topic_meta' => array(
+				'forum_id' => $f1,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t1,
+			'reply_meta' => array(
+				'forum_id' => $f1,
+				'topic_id' => $t1,
+			),
+		) );
+
+		$id = bbp_update_forum_last_active_id( $f1, $r1 );
+		$this->assertSame( $r1, $id );
+
+		$id = bbp_get_forum_last_active_id( $f1 );
+		$this->assertSame( $r1, $id );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f2,
+			'topic_meta' => array(
+				'forum_id' => $f2,
+			),
+		) );
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t2,
+			'reply_meta' => array(
+				'forum_id' => $f2,
+				'topic_id' => $t2,
+			),
+		) );
+
+		bbp_update_forum_last_active_id( $f2 );
+		$id = bbp_get_forum_last_active_id( $f2 );
+		$this->assertSame( $r2, $id );
+
+		bbp_update_forum_last_active_id( $f1 );
+		$id = bbp_get_forum_last_active_id( $f1 );
+		$this->assertSame( $r2, $id );
+	}
+
+	/**
+	 * @covers ::bbp_update_forum_last_active_time
+	 */
+	public function test_bbp_update_forum_last_active_time() {
+		$f1 = $this->factory->forum->create();
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_parent' => $f1,
+			'topic_meta' => array(
+				'forum_id' => $f1,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t1,
+			'reply_meta' => array(
+				'forum_id' => $f1,
+				'topic_id' => $t1,
+			),
+		) );
+
+		$r1_time_raw       = get_post_field( 'post_date', $r1 );
+		$r1_time_formatted = bbp_get_time_since( bbp_convert_date( $r1_time_raw ) );
+
+		$time = bbp_update_forum_last_active_time( $f1, $r1_time_raw );
+		$this->assertSame( $r1_time_raw, $time );
+
+		$time = bbp_get_forum_last_active_time( $f1 );
+		$this->assertSame( $r1_time_formatted, $time );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_parent' => $f2,
+			'topic_meta' => array(
+				'forum_id' => $f2,
+			),
+		) );
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t2,
+			'reply_meta' => array(
+				'forum_id' => $f2,
+				'topic_id' => $t2,
+			),
+		) );
+
+		$r2_time_raw       = get_post_field( 'post_date', $r2 );
+		$r2_time_formatted = bbp_get_time_since( bbp_convert_date( $r2_time_raw ) );
+
+		bbp_update_forum_last_active_time( $f2 );
+		$time = bbp_get_forum_last_active_time( $f2 );
+		$this->assertSame( $r2_time_formatted, $time );
+
+		bbp_update_forum_last_active_time( $f1 );
+		$time = bbp_get_forum_last_active_time( $f1 );
+		$this->assertSame( $r2_time_formatted, $time );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/functions/visibility.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/functions/visibility.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/functions/visibility.php	(revision 7162)
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Tests for bbPress forum visibility functions.
+ *
+ * @group forums
+ * @group functions
+ * @group visibility
+ */
+class BBP_Tests_Forums_Functions_Visibility extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_repair_forum_visibility
+	 * @todo   Implement test_bbp_repair_forum_visibility().
+	 */
+	public function test_bbp_repair_forum_visibility() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_visibilities
+	 * @todo   Implement test_bbp_get_forum_visibilities().
+	 */
+	public function test_bbp_get_forum_visibilities() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_hidden_forum_ids
+	 * @todo   Implement test_bbp_get_hidden_forum_ids().
+	 */
+	public function test_bbp_get_hidden_forum_ids() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_private_forum_ids
+	 * @todo   Implement test_bbp_get_private_forum_ids().
+	 */
+	public function test_bbp_get_private_forum_ids() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_pre_get_posts_normalize_forum_visibility
+	 * @todo   Implement test_bbp_pre_get_posts_normalize_forum_visibility().
+	 */
+	public function test_bbp_pre_get_posts_normalize_forum_visibility() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_enforce_hidden
+	 * @todo   Implement test_bbp_forum_enforce_hidden().
+	 */
+	public function test_bbp_forum_enforce_hidden() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_enforce_private
+	 * @todo   Implement test_bbp_forum_enforce_private().
+	 */
+	public function test_bbp_forum_enforce_private() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/authors.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/authors.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/authors.php	(revision 7162)
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_author_*()` template functions.
+ *
+ * @group forums
+ * @group template
+ * @group authors
+ */
+class BBP_Tests_Forums_Template_Authors extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_author_id
+	 * @covers ::bbp_get_forum_author_id
+	 */
+	public function test_bbp_get_forum_author_id() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create( array(
+			'post_author' => $u,
+		) );
+
+		$forum = bbp_get_forum_author_id( $f );
+		$this->assertSame( $u, $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_author_display_name
+	 * @covers ::bbp_get_forum_author_display_name
+	 */
+	public function test_bbp_get_forum_author_display_name() {
+		$u = $this->factory->user->create( array(
+			'display_name' => 'Barry B. Benson',
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_author' => $u,
+		) );
+
+		$forum = bbp_get_forum_author_display_name( $f );
+		$this->assertSame( 'Barry B. Benson', $forum );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/counts.php	(revision 7162)
@@ -0,0 +1,258 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_forum_*_count()` template functions.
+ *
+ * @group forums
+ * @group template
+ * @group counts
+ */
+class BBP_Tests_Forums_Template_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_subforum_count
+	 * @covers ::bbp_get_forum_subforum_count
+	 */
+	public function test_bbp_get_forum_subforum_count() {
+		$f1 = $this->factory->forum->create();
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->forum->create_many( $int_value, array(
+			'post_parent' => $f1,
+		) );
+
+		bbp_update_forum_subforum_count( $f1 );
+
+		// Output.
+		$count = bbp_get_forum_subforum_count( $f1, false );
+		$this->expectOutputString( $formatted_value );
+		bbp_forum_subforum_count( $f1 );
+
+		// Formatted string.
+		$count = bbp_get_forum_subforum_count( $f1, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Integer.
+		$count = bbp_get_forum_subforum_count( $f1, true );
+		$this->assertSame( $int_value, $count );
+
+		// Direct query.
+		$count = count( bbp_forum_query_subforum_ids( $f1 ) );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_forum_topic_count
+	 * @covers ::bbp_get_forum_topic_count
+	 */
+	public function test_bbp_get_forum_topic_count() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+			),
+		) );
+
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->topic->create_many( $int_value, array(
+			'post_parent' => $f,
+		) );
+
+		bbp_update_forum_topic_count( $c );
+		bbp_update_forum_topic_count( $f );
+
+		// Forum output.
+		$count = bbp_get_forum_topic_count( $f, true, false );
+		$this->expectOutputString( $formatted_value );
+		bbp_forum_topic_count( $f );
+
+		// Forum formatted string.
+		$count = bbp_get_forum_topic_count( $f, true, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Forum integer.
+		$count = bbp_get_forum_topic_count( $f, true, true );
+		$this->assertSame( $int_value, $count );
+
+		// Category topic count.
+		$count = bbp_get_forum_topic_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count.
+		$count = bbp_get_forum_topic_count( $c, true, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_forum_reply_count
+	 * @covers ::bbp_get_forum_reply_count
+	 */
+	public function test_bbp_get_forum_reply_count() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+		) );
+
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->reply->create_many( $int_value, array(
+			'post_parent' => $t,
+		) );
+
+		bbp_update_forum_reply_count( $c );
+		bbp_update_forum_reply_count( $f );
+
+		// Forum Output.
+		$count = bbp_get_forum_reply_count( $f, true, false );
+		$this->expectOutputString( $formatted_value );
+		bbp_forum_reply_count( $f );
+
+		// Forum formatted string.
+		$count = bbp_get_forum_reply_count( $f, true, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Forum integer.
+		$count = bbp_get_forum_reply_count( $f, true, true );
+		$this->assertSame( $int_value, $count );
+
+		// Category reply count.
+		$count = bbp_get_forum_reply_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total reply count.
+		$count = bbp_get_forum_reply_count( $c, true, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_forum_post_count
+	 * @covers ::bbp_get_forum_post_count
+	 */
+	public function test_bbp_get_forum_post_count() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+		) );
+
+		$int_value = 3;
+
+		// Topic + Replies.
+		$result = 4;
+		$formatted_result = bbp_number_format( $result );
+
+		$this->factory->reply->create_many( $int_value, array(
+			'post_parent' => $t,
+		) );
+
+		bbp_update_forum_topic_count( $c );
+		bbp_update_forum_topic_count( $f );
+		bbp_update_forum_reply_count( $c );
+		bbp_update_forum_reply_count( $f );
+
+		// Forum output.
+		$count = bbp_get_forum_post_count( $f, true, false );
+		$this->expectOutputString( $formatted_result );
+		bbp_forum_post_count( $f );
+
+		// Forum formatted string.
+		$count = bbp_get_forum_post_count( $f, true, false );
+		$this->assertSame( $formatted_result, $count );
+
+		// Forum integer.
+		$count = bbp_get_forum_post_count( $f, true, true );
+		$this->assertSame( $result, $count );
+
+		// Category post count.
+		$count = bbp_get_forum_post_count( $c, false, true );
+		$this->assertSame( 0, $count );
+
+		// Category total post count.
+		$count = bbp_get_forum_post_count( $c, true, true );
+		$this->assertSame( $result, $count );
+	}
+
+	/**
+	 * @covers ::bbp_forum_topic_count_hidden
+	 * @covers ::bbp_get_forum_topic_count_hidden
+	 */
+	public function test_bbp_get_forum_topic_count_hidden() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+			),
+		) );
+
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->topic->create_many( $int_value, array(
+			'post_parent' => $f,
+			'post_status' => bbp_get_spam_status_id(),
+		) );
+
+		bbp_update_forum_topic_count_hidden( $c );
+		bbp_update_forum_topic_count_hidden( $f );
+
+		// Forum output.
+		$count = bbp_get_forum_topic_count_hidden( $f, false );
+		$this->expectOutputString( $formatted_value );
+		bbp_forum_topic_count_hidden( $f );
+
+		// Forum formatted string.
+		$count = bbp_get_forum_topic_count_hidden( $f, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Forum integer.
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( $int_value, $count );
+
+		// Category topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+
+		// Category total topic count hidden.
+		$count = bbp_get_forum_topic_count_hidden( $c, true );
+		$this->assertSame( 0, $count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/feeds.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/feeds.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/feeds.php	(revision 7162)
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_*_feed_link()` functions.
+ *
+ * @group forums
+ * @group template
+ * @group feeds
+ */
+class BBP_Tests_Forums_Template_Feeds extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_topics_feed_link
+	 * @covers ::bbp_get_forum_topics_feed_link
+	 */
+	public function test_bbp_get_forum_topics_feed_link() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Forum 1',
+		) );
+
+		$feed_link = bbp_get_forum_topics_feed_link( $f );
+		$this->expectOutputString( $feed_link );
+		bbp_forum_topics_feed_link( $f );
+
+		$feed_link = bbp_get_forum_topics_feed_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?feed=rss2&#038;forum=forum-1" class="bbp-forum-rss-link topics"><span>Topics</span></a>', $feed_link );
+	}
+
+	/**
+	 * @covers ::bbp_forum_replies_feed_link
+	 * @covers ::bbp_get_forum_replies_feed_link
+	 */
+	public function test_bbp_get_forum_replies_feed_link() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Forum 1',
+		) );
+
+		$feed_link = bbp_get_forum_replies_feed_link( $f );
+		$this->expectOutputString( $feed_link );
+		bbp_forum_replies_feed_link( $f );
+
+		$feed_link = bbp_get_forum_replies_feed_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?type=reply&#038;feed=rss2&#038;forum=forum-1" class="bbp-forum-rss-link replies"><span>Replies</span></a>', $feed_link );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/forms.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/forms.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/forms.php	(revision 7162)
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_*_()` functions.
+ *
+ * @group forums
+ * @group template
+ * @group forms
+ */
+class BBP_Tests_Forums_Template_Forms extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_form_forum_title
+	 * @covers ::bbp_get_form_forum_title
+	 * @todo   Implement test_bbp_form_forum_title().
+	 * @todo   Implement test_bbp_get_form_forum_title().
+	 */
+	public function test_bbp_get_form_forum_title() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_content
+	 * @covers ::bbp_get_form_forum_content
+	 * @todo   Implement test_bbp_form_forum_content().
+	 * @todo   Implement test_bbp_get_form_forum_content().
+	 */
+	public function test_bbp_get_form_forum_content() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_parent
+	 * @covers ::bbp_get_form_forum_parent
+	 * @todo   Implement test_bbp_form_forum_parent().
+	 * @todo   Implement test_bbp_get_form_forum_parent().
+	 */
+	public function test_bbp_get_form_forum_parent() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_type
+	 * @covers ::bbp_get_form_forum_type
+	 * @todo   Implement test_bbp_form_forum_type().
+	 * @todo   Implement test_bbp_get_form_forum_type().
+	 */
+	public function test_bbp_get_form_forum_type() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_visibility
+	 * @covers ::bbp_get_form_forum_visibility
+	 * @todo   Implement test_bbp_form_forum_visibility().
+	 * @todo   Implement test_bbp_get_form_forum_visibility().
+	 */
+	public function test_bbp_get_form_forum_visibility() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_subscribed
+	 * @covers ::bbp_get_form_forum_subscribed
+	 * @todo   Implement test_bbp_form_forum_subscribed().
+	 * @todo   Implement test_bbp_get_form_forum_subscribed().
+	 */
+	public function test_bbp_get_form_forum_subscribed() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_type_dropdown
+	 * @covers ::bbp_get_form_forum_type_dropdown
+	 * @todo   Implement test_bbp_form_forum_type_dropdown().
+	 * @todo   Implement test_bbp_get_form_forum_type_dropdown().
+	 */
+	public function test_bbp_get_form_forum_type_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_status_dropdown
+	 * @covers ::bbp_get_form_forum_status_dropdown
+	 * @todo   Implement test_bbp_form_forum_status_dropdown().
+	 * @todo   Implement test_bbp_get_form_forum_status_dropdown().
+	 */
+	public function test_bbp_get_form_forum_status_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_forum_visibility_dropdown
+	 * @covers ::bbp_get_form_forum_visibility_dropdown
+	 * @todo   Implement test_bbp_form_forum_visibility_dropdown().
+	 * @todo   Implement test_bbp_get_form_forum_visibility_dropdown().
+	 */
+	public function test_bbp_get_form_forum_visibility_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_form_post_request
+	 * @todo   Implement test_bbp_is_forum_form_post_request().
+	 */
+	public function test_bbp_is_forum_form_post_request() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/forum.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/forum.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/forum.php	(revision 7162)
@@ -0,0 +1,385 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_forum_*()` template functions.
+ *
+ * @group forums
+ * @group template
+ * @group forum
+ */
+class BBP_Tests_Forums_Template_Forum extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_id
+	 * @covers ::bbp_get_forum_id
+	 */
+	public function test_bbp_get_forum_id() {
+		$f = $this->factory->forum->create();
+
+		$forum_id = bbp_get_forum_id( $f );
+		$this->assertSame( $f, $forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_forum
+	 * @todo   Implement test_bbp_get_forum().
+	 */
+	public function test_bbp_get_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_permalink
+	 * @covers ::bbp_get_forum_permalink
+	 */
+	public function test_bbp_get_forum_permalink() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		// Public category.
+		$c = $this->factory->forum->create( array(
+			'post_title' => 'Public Category',
+		) );
+
+		$category = bbp_get_forum_permalink( $c );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=public-category', $category );
+
+		// Public forum of public category.
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Public Forum',
+			'post_parent' => $c,
+		) );
+
+		$forum_permalink = bbp_get_forum_permalink( $f );
+		$this->expectOutputString( $forum_permalink );
+		bbp_forum_permalink( $f );
+
+		$forum = bbp_get_forum_permalink( $f );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=public-category/public-forum', $forum );
+
+		// Private category.
+		$c = $this->factory->forum->create( array(
+			'post_title' => 'Private Category',
+		) );
+
+		$category = bbp_get_forum_permalink( $c );
+
+		$forum = bbp_get_forum_permalink( $f );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=private-category', $category );
+
+		// Private forum of private category.
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Private Forum',
+			'post_parent' => $c,
+		) );
+
+		bbp_privatize_forum( $c );
+		$forum = bbp_get_forum_permalink( $f );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=private-category/private-forum', $forum );
+
+		// Hidden category.
+		$c = $this->factory->forum->create( array(
+			'post_title' => 'Hidden Category',
+		) );
+
+		bbp_hide_forum( $c );
+		$category = bbp_get_forum_permalink( $c );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=hidden-category', $category );
+
+		// Hidden forum of hidden category.
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Hidden Forum',
+			'post_parent' => $c,
+		) );
+
+		$forum = bbp_get_forum_permalink( $f );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?forum=hidden-category/hidden-forum', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_title
+	 * @covers ::bbp_get_forum_title
+	 */
+	public function test_bbp_get_forum_title() {
+		$f = $this->factory->forum->create( array(
+			'post_title' => 'Forum 1',
+		) );
+
+		$forum = bbp_get_forum_title( $f );
+		$this->assertSame( 'Forum 1', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_archive_title
+	 * @covers ::bbp_get_forum_archive_title
+	 * @todo   Implement test_bbp_forum_archive_title().
+	 * @todo   Implement test_bbp_get_forum_archive_title().
+	 */
+	public function test_bbp_get_forum_archive_title() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_content
+	 * @covers ::bbp_get_forum_content
+	 */
+	public function test_bbp_get_forum_content() {
+		$f = $this->factory->forum->create( array(
+			'post_content' => 'Content of Forum 1',
+		) );
+
+		$forum = bbp_get_forum_content( $f );
+		$this->assertSame( 'Content of Forum 1', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_freshness_link
+	 * @covers ::bbp_get_forum_freshness_link
+	 */
+	public function test_bbp_get_forum_freshness_link() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60*60*100 );
+
+		$f = $this->factory->forum->create();
+
+		$fresh_link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( 'No Topics', $fresh_link );
+
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$fresh_link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="Topic 1">4 days, 4 hours ago</a>', $fresh_link );
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_freshness_link
+	 */
+	public function test_bbp_get_forum_freshness_link_with_unpublished_replies() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$now = time();
+		$post_date_t1 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 18 ); // 18 hours ago
+		$post_date_t2 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 16 ); // 16 hours ago
+		$post_date_t3 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 14 ); // 14 hours ago
+		$post_date_t4 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 12 ); // 12 hours ago
+		$post_date_t5 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 10 ); // 1o hours ago
+
+		$f = $this->factory->forum->create();
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( 'No Topics', $link );
+
+		$t1 = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'post_date' => $post_date_t1,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="Topic 1">18 hours ago</a>', $link );
+
+		$t2 = $this->factory->topic->create( array(
+			'post_title' => 'Topic 2',
+			'post_parent' => $f,
+			'post_date' => $post_date_t2,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-2" title="Topic 2">16 hours ago</a>', $link );
+
+		bbp_spam_topic( $t2 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="Topic 1">18 hours ago</a>', $link );
+
+		$t3 = $this->factory->topic->create( array(
+			'post_title' => 'Topic 3',
+			'post_parent' => $f,
+			'post_date' => $post_date_t3,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-3" title="Topic 3">14 hours ago</a>', $link );
+
+		// Todo: Use bbp_trash_topic() and not wp_trash_post()
+		wp_trash_post( $t3 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="Topic 1">18 hours ago</a>', $link );
+
+		$t4 = $this->factory->topic->create( array(
+			'post_title' => 'Topic 4',
+			'post_parent' => $f,
+			'post_date' => $post_date_t4,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-4" title="Topic 4">12 hours ago</a>', $link );
+
+		bbp_unapprove_topic( $t4 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="Topic 1">18 hours ago</a>', $link );
+
+		bbp_unspam_topic( $t2 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-2" title="Topic 2">16 hours ago</a>', $link );
+
+		// Todo: Use bbp_untrash_topic() and not wp_untrash_post()
+		wp_untrash_post( $t3 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-3" title="Topic 3">14 hours ago</a>', $link );
+
+		bbp_approve_topic( $t4 );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-4" title="Topic 4">12 hours ago</a>', $link );
+
+		$t5 = $this->factory->topic->create( array(
+			'post_title' => 'Topic 5',
+			'post_parent' => $f,
+			'post_date' => $post_date_t5,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_forum_freshness_link( $f );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-5" title="Topic 5">10 hours ago</a>', $link );
+	}
+
+	/**
+	 * @covers ::bbp_forum_parent_id
+	 * @covers ::bbp_get_forum_parent_id
+	 */
+	public function test_bbp_get_forum_parent_id() {
+		$f1 = $this->factory->forum->create();
+
+		$forum_id = bbp_get_forum_parent_id( $f1 );
+		$this->assertSame( 0, $forum_id );
+
+		$f2 = $this->factory->forum->create( array(
+			'post_parent' => $f1,
+		) );
+
+		$forum_id = bbp_get_forum_parent_id( $f2 );
+		$this->assertSame( $f1, $forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_ancestors
+	 * @todo   Implement test_bbp_get_forum_ancestors().
+	 */
+	public function test_bbp_get_forum_ancestors() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_get_subforums
+	 * @todo   Implement test_bbp_forum_get_subforums().
+	 */
+	public function test_bbp_forum_get_subforums() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_list_forums
+	 * @todo   Implement test_bbp_list_forums().
+	 */
+	public function test_bbp_list_forums() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_subscription_link
+	 * @covers ::bbp_get_forum_subscription_link
+	 * @todo   Implement test_bbp_get_forum_subscription_link().
+	 */
+	public function test_bbp_get_forum_subscription_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_topics_link
+	 * @covers ::bbp_get_forum_topics_link
+	 * @todo   Implement test_bbp_get_forum_topics_link().
+	 */
+	public function test_bbp_get_forum_topics_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_class
+	 * @covers ::bbp_get_forum_class
+	 * @todo   Implement test_bbp_get_forum_class().
+	 */
+	public function test_bbp_get_forum_class() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_single_forum_description
+	 * @covers ::bbp_get_single_forum_description
+	 */
+	public function test_bbp_get_single_forum_description() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_get_single_forum_description( $f );
+		$this->assertSame( '<div class="bbp-template-notice info"><ul><li class="bbp-forum-description">This forum is empty.</li></ul></div>', $forum );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/get-last-thing.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/get-last-thing.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/get-last-thing.php	(revision 7162)
@@ -0,0 +1,813 @@
+<?php
+
+/**
+ * Tests for the `bbp_get_forum_last_*()` template functions.
+ *
+ * @group forums
+ * @group template
+ * @group get_last_thing
+ */
+class BBP_Tests_Forums_Template_Forum_Last_Thing extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_last_active_id
+	 * @covers ::bbp_get_forum_last_active_id
+	 */
+	public function test_bbp_get_forum_last_active_id() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $t, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( $t, $last_id );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( $r, $last_id );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_active_id
+	 * @covers ::bbp_get_forum_last_active_id
+	 */
+	public function test_bbp_get_forum_last_active_id_with_pending_reply() {
+		$u = $this->factory->user->create_many( 2 );
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $t, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( $t, $last_id );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_update_forum_last_active_id( $f );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r1, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+
+		$this->assertSame( $r1, $last_id );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u[1],
+			'post_status' => bbp_get_pending_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r1, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( $r1, $last_id );
+
+		bbp_approve_reply( $r2 );
+
+		// Get the forums last active id.
+		$last_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r2, $last_id );
+
+		// Get the categories last active id.
+		$last_id = bbp_get_forum_last_active_id( $c );
+		$this->assertSame( $r2, $last_id );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_active_time
+	 * @covers ::bbp_get_forum_last_active_time
+	 */
+	public function test_bbp_get_forum_last_active_time() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+
+		// Get the forums last active time.
+		$last_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( '', $last_time );
+
+		// Get the categories last active time.
+		$last_time = bbp_get_forum_last_active_time( $c );
+		$this->assertSame( '', $last_time );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_last_active_time( $f );
+
+		// Get the forums last active time.
+		$last_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( '4 days, 4 hours ago', $last_time );
+
+		// Get the categories last active time.
+		$last_time = bbp_get_forum_last_active_time( $c );
+		$this->assertSame( '4 days, 4 hours ago', $last_time );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_update_forum_last_active_time( $f );
+
+		// Get the forums last active time.
+		$last_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( '4 days, 4 hours ago', $last_time );
+
+		// Get the categories last active time.
+		$last_time = bbp_get_forum_last_active_time( $c );
+		$this->assertSame( '4 days, 4 hours ago', $last_time );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_topic_id
+	 * @covers ::bbp_get_forum_last_topic_id
+	 */
+	public function test_bbp_get_forum_last_topic_id() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'        => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		// Get the forums last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		bbp_update_forum_last_topic_id( $f );
+
+		// Get the forums last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $f );
+		$this->assertSame( 0, $last_id );
+
+		// Get the categories last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $c );
+		$this->assertSame( 0, $last_id );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		bbp_update_forum_last_topic_id( $f );
+
+		// Get the forums last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $f );
+		$this->assertSame( $t, $last_id );
+
+		// Get the categories last topic id.
+		$last_id = bbp_get_forum_last_topic_id( $c );
+		$this->assertSame( $t, $last_id );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_topic_title
+	 * @covers ::bbp_get_forum_last_topic_title
+	 */
+	public function test_bbp_get_forum_last_topic_title() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'        => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last topic title.
+		$forum = bbp_get_forum_last_topic_title( $f );
+		$this->assertSame( 'Topic 1', $forum );
+
+		// Get the categories last topic title.
+		$category = bbp_get_forum_last_topic_title( $c );
+		$this->assertSame( 'Topic 1', $category );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_topic_permalink
+	 * @covers ::bbp_get_forum_last_topic_permalink
+	 */
+	public function test_bbp_get_forum_last_topic_permalink() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multisite for now.' );
+		}
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Get the forums last topic permalink.
+		$forum_last_topic_permalink = bbp_get_forum_last_topic_permalink( $f );
+		$this->assertSame( bbp_get_topic_permalink( $t ), $forum_last_topic_permalink );
+
+		// Get the categories last topic permalink.
+		$forum_last_topic_permalink = bbp_get_forum_last_topic_permalink( $c );
+		$this->assertSame( bbp_get_topic_permalink( $t ), $forum_last_topic_permalink );
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_last_topic_author_id
+	 */
+	public function test_bbp_get_forum_last_topic_author_id() {
+		$u = $this->factory->user->create();
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_author' => $u,
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last author id.
+		$forum = bbp_get_forum_last_topic_author_id( $f );
+		$this->assertSame( $u, $forum );
+
+		// Get the categories last author id.
+		$forum = bbp_get_forum_last_topic_author_id( $c );
+		$this->assertSame( $u, $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_topic_author_link
+	 * @covers ::bbp_get_forum_last_topic_author_link	 */
+	public function test_bbp_get_forum_last_topic_author_link() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multisite for now.' );
+		}
+
+		$u = $this->factory->user->create();
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Get the forums last topic author link.
+		$last_topic_author_link = bbp_get_forum_last_topic_author_link( $f );
+		$this->assertSame( bbp_get_user_profile_link( $u ), $last_topic_author_link );
+
+		// Get the categories last topic author link.
+		$last_topic_author_link = bbp_get_forum_last_topic_author_link( $c );
+		$this->assertSame( bbp_get_user_profile_link( $u ), $last_topic_author_link );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_id
+	 * @covers ::bbp_get_forum_last_reply_id
+	 */
+	public function test_bbp_get_forum_last_reply_id() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply id.
+		$last_reply_id_f = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $r1, $last_reply_id_f );
+
+		// Get the categories last reply id.
+		$last_reply_id_c = bbp_get_forum_last_reply_id( $c );
+		$this->assertSame( $r1, $last_reply_id_c );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply id.
+		$last_reply_id_f = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $r2, $last_reply_id_f );
+
+		// Get the categories last reply id.
+		$last_reply_id_c = bbp_get_forum_last_reply_id( $c );
+		$this->assertSame( $r2, $last_reply_id_c );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_title
+	 * @covers ::bbp_get_forum_last_reply_title
+	 */
+	public function test_bbp_get_forum_last_reply_title() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$this->factory->reply->create( array(
+			'post_title' => 'Reply To: Topic 1',
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply title.
+		$forum = bbp_get_forum_last_reply_title( $f );
+		$this->assertSame( 'Reply To: Topic 1', $forum );
+
+		// Get the categories last reply title.
+		$category = bbp_get_forum_last_reply_title( $c );
+		$this->assertSame( 'Reply To: Topic 1', $category );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_permalink
+	 * @covers ::bbp_get_forum_last_reply_permalink
+	 * @todo   Implement test_bbp_get_forum_last_reply_permalink().
+	 */
+	public function test_bbp_get_forum_last_reply_permalink() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_url
+	 * @covers ::bbp_get_forum_last_reply_url
+	 * @todo   Implement test_bbp_get_forum_last_reply_url().
+	 */
+	public function test_bbp_get_forum_last_reply_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_author_id
+	 * @covers ::bbp_get_forum_last_reply_author_id
+	 */
+	public function test_bbp_get_forum_last_reply_author_id() {
+		$u = $this->factory->user->create();
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_author' => $u,
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply author id.
+		$forum = bbp_get_forum_last_reply_author_id( $f );
+		$this->assertSame( $u, $forum );
+
+		// Get the categories last reply author id.
+		$category = bbp_get_forum_last_reply_author_id( $c );
+		$this->assertSame( $u, $category );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_reply_author_link
+	 * @covers ::bbp_get_forum_last_reply_author_link
+	 */
+	public function test_bbp_get_forum_last_reply_author_link() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multisite for now.' );
+		}
+
+		$u = $this->factory->user->create();
+
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_author' => $u,
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the forums last reply author link.
+		$last_reply_author_link = bbp_get_forum_last_reply_author_link( $f );
+		$this->assertSame( bbp_get_user_profile_link( $u ), $last_reply_author_link );
+
+		// Get the categories last reply author link.
+		$last_reply_author_link = bbp_get_forum_last_reply_author_link( $c );
+		$this->assertSame( bbp_get_user_profile_link( $u ), $last_reply_author_link );
+	}
+
+	/**
+	 * @covers ::bbp_forum_last_topic_id
+	 * @covers ::bbp_get_forum_last_topic_id
+	 * @covers ::bbp_forum_last_reply_id
+	 * @covers ::bbp_get_forum_last_reply_id
+	 * @covers ::bbp_topic_last_reply_id
+	 * @covers ::bbp_get_topic_last_reply_id
+	 */
+	public function test_bbp_get_forum_and_topic_last_topic_id_and_last_reply_id() {
+		$c = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+				'status'     => 'open',
+			),
+		) );
+
+
+		$f = $this->factory->forum->create( array(
+			'post_parent' => $c,
+			'forum_meta' => array(
+				'forum_id'   => $c,
+				'forum_type' => 'forum',
+				'status'     => 'open',
+			),
+		) );
+
+		// Get the forums last topic id _bbp_last_topic_id.
+		$this->assertSame( 0, bbp_get_forum_last_topic_id( $f ) );
+
+		// Get the category last topic id _bbp_last_topic_id.
+		$this->assertSame( 0, bbp_get_forum_last_topic_id( $c ) );
+
+		// Get the forums last reply id _bbp_last_reply_id.
+		$this->assertSame( 0, bbp_get_forum_last_reply_id( $f ) );
+
+		// Get the category last reply id _bbp_last_reply_id.
+		$this->assertSame( 0, bbp_get_forum_last_reply_id( $c ) );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Get the forums last topic id _bbp_last_topic_id.
+		$this->assertSame( $t, bbp_get_forum_last_topic_id( $f ) );
+
+		// Get the category last topic id _bbp_last_topic_id.
+		$this->assertSame( $t, bbp_get_forum_last_topic_id( $c ) );
+
+		// Create another reply.
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		// Get the forums last reply id _bbp_last_reply_id.
+		$this->assertSame( $r, bbp_get_forum_last_reply_id( $f ) );
+
+		// Get the category last reply id _bbp_last_reply_id.
+		$this->assertSame( $r, bbp_get_forum_last_reply_id( $c ) );
+
+		// Get the topics last reply id _bbp_last_reply_id.
+		$this->assertSame( $r, bbp_get_topic_last_reply_id( $t ) );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/loop.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/loop.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/loop.php	(revision 7162)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Tests for the forum loop functions.
+ *
+ * @group forums
+ * @group template
+ * @group loop
+ */
+class BBP_Tests_Forums_Template_Forum_Loop extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_has_forums
+	 * @todo   Implement test_bbp_has_forums().
+	 */
+	public function test_bbp_has_forums() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forums
+	 * @todo   Implement test_bbp_forums().
+	 */
+	public function test_bbp_forums() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_the_forum
+	 * @todo   Implement test_bbp_the_forum().
+	 */
+	public function test_bbp_the_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/post_type.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/post_type.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/post_type.php	(revision 7162)
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_post_type_*()` functions.
+ *
+ * @group forums
+ * @group template
+ * @group post_type
+ */
+class BBP_Tests_Forums_Template_Post_Type extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_post_type
+	 * @covers ::bbp_get_forum_post_type
+	 */
+	public function test_bbp_get_forum_post_type() {
+		$f = $this->factory->forum->create();
+
+		$fobj = get_post_type_object( 'forum' );
+
+		// WordPress 4.6 introduced `WP_Post_Type` class
+		if ( bbp_get_major_wp_version() < 4.6 ) {
+			$this->assertInstanceOf( 'stdClass', $fobj );
+		} else {
+			$this->assertInstanceOf( 'WP_Post_Type', $fobj );
+		}
+
+		$this->assertEquals( 'forum', $fobj->name );
+
+		// Test some defaults
+		$this->assertTrue( is_post_type_hierarchical( 'forum' ) );
+
+		$forum_type = bbp_forum_post_type( $f );
+		$this->expectOutputString( 'forum', $forum_type );
+
+		$forum_type = bbp_get_forum_post_type( $f );
+		$this->assertSame( 'forum', $forum_type );
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_post_type_labels
+	 * @todo   Implement test_bbp_get_forum_post_type_labels().
+	 */
+	public function test_bbp_get_forum_post_type_labels() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_post_type_rewrite
+	 * @todo   Implement test_bbp_get_forum_post_type_rewrite().
+	 */
+	public function test_bbp_get_forum_post_type_rewrite() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_forum_post_type_supports
+	 * @todo   Implement test_bbp_get_forum_post_type_supports().
+	 */
+	public function test_bbp_get_forum_post_type_supports() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/status.php	(revision 7162)
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_*` status template functions.
+ *
+ * @group forums
+ * @group template
+ * @group status
+ */
+class BBP_Tests_Forums_Template_Status extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_status
+	 * @covers ::bbp_get_forum_status
+	 */
+	public function test_bbp_get_forum_status() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_get_forum_status( $f );
+		$this->assertSame( 'open', $forum );
+
+		bbp_normalize_forum( $f );
+		$this->assertSame( 'open', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_forum_type
+	 * @covers ::bbp_get_forum_type
+	 */
+	public function test_bbp_get_forum_type() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_get_forum_type( $f );
+		$this->assertSame( 'forum', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_category
+	 */
+	public function test_bbp_is_forum_category() {
+		$f = $this->factory->forum->create_many( 2 );
+
+		bbp_normalize_forum( $f[0] );
+
+		$categorize_meta = get_post_meta( $f[0], '_bbp_forum_type', true );
+		$this->assertSame( 'forum', $categorize_meta );
+
+		$forum = bbp_is_forum_category( $f[0] );
+		$this->assertFalse( $forum );
+
+		bbp_categorize_forum( $f[1] );
+
+		$categorize_meta = get_post_meta( $f[1], '_bbp_forum_type', true );
+		$this->assertSame( 'category', $categorize_meta );
+
+		$forum = bbp_is_forum_category( $f[1] );
+		$this->assertTrue( $forum );
+
+		$f = $this->factory->forum->create( array(
+			'forum_meta' => array(
+				'forum_type' => 'category',
+			)
+		) );
+
+		$forum = bbp_is_forum_category( $f );
+		$this->assertTrue( $forum );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_open
+	 */
+	public function test_bbp_is_forum_open() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_is_forum_open( $f );
+		$this->assertTrue( $forum );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_closed
+	 */
+	public function test_bbp_is_forum_closed() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_is_forum_closed( $f );
+		$this->assertFalse( $forum );
+
+		bbp_close_forum( $f );
+
+		$forum = bbp_is_forum_closed( $f );
+		$this->assertTrue( $forum );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_status
+	 * @todo   Implement test_bbp_is_forum_status().
+	 */
+	public function test_bbp_is_forum_status() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_public
+	 */
+	public function test_bbp_is_forum_public() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_get_forum_visibility( $f );
+		$this->assertSame( 'publish', $forum );
+
+		$forum_status_id = bbp_get_public_status_id( $f );
+		$this->assertSame( 'publish', $forum_status_id );
+
+		$forum = bbp_is_forum_public( $f );
+		$this->assertTrue( bbp_is_forum_public( $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_private
+	 */
+	public function test_bbp_is_forum_private() {
+		$f = $this->factory->forum->create( array(
+			'post_status' => 'private',
+		) );
+
+		$forum = bbp_get_forum_visibility( $f );
+		$this->assertSame( 'private', $forum );
+
+		$forum_status_id = bbp_get_private_status_id( $f );
+		$this->assertSame( 'private', $forum_status_id );
+
+		$this->assertTrue( bbp_is_forum_private( $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_hidden
+	 */
+	public function test_bbp_is_forum_hidden() {
+		$f = $this->factory->forum->create( array(
+			'post_status' => 'hidden',
+		) );
+
+		$forum = bbp_get_forum_visibility( $f );
+		$this->assertSame( 'hidden', $forum );
+
+		$forum_status_id = bbp_get_hidden_status_id( $f );
+		$this->assertSame( 'hidden', $forum_status_id );
+
+		$this->assertTrue( bbp_is_forum_hidden( $f ) );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/forums/template/visibility.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/forums/template/visibility.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/forums/template/visibility.php	(revision 7162)
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_forum_*` visibility template functions.
+ *
+ * @group forums
+ * @group template
+ * @group visibility
+ */
+class BBP_Tests_Forums_Template_Visibility extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_forum_visibility
+	 * @covers ::bbp_get_forum_visibility
+	 */
+	public function test_bbp_get_forum_visibility() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_get_forum_visibility( $f, bbp_get_public_status_id(), false );
+		$this->assertSame( 'publish', $forum );
+
+		$f = $this->factory->forum->create( array(
+			'post_status' => bbp_get_private_status_id()
+		) );
+
+		//bbp_privatize_forum( $f );
+		$forum = bbp_get_forum_visibility( $f, bbp_get_private_status_id(), false );
+		$this->assertSame( 'private', $forum );
+
+		$f = $this->factory->forum->create();
+
+		bbp_hide_forum( $f );
+		$forum = bbp_get_forum_visibility( $f, bbp_get_hidden_status_id(), false );
+		$this->assertSame( 'hidden', $forum );
+	}
+
+	/**
+	 * @covers ::bbp_is_forum_visibility
+	 */
+	public function test_bbp_is_forum_visibility() {
+		$f = $this->factory->forum->create();
+
+		$forum = bbp_is_forum_visibility( $f, bbp_get_public_status_id(), false );
+		$this->assertTrue( $forum );
+
+		$f = $this->factory->forum->create( array(
+			'post_status' => bbp_get_private_status_id()
+		) );
+
+		$forum = bbp_is_forum_visibility( $f, bbp_get_private_status_id(), false );
+		$this->assertTrue( $forum );
+
+		$f = $this->factory->forum->create();
+
+		bbp_hide_forum( $f );
+		$forum = bbp_is_forum_visibility( $f, bbp_get_hidden_status_id(), false );
+		$this->assertTrue( $forum );
+	}
+
+	/**
+	 * @covers ::bbp_suppress_private_forum_meta
+	 * @todo   Implement test_bbp_suppress_private_forum_meta().
+	 */
+	public function test_bbp_suppress_private_forum_meta() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_suppress_private_author_link
+	 * @todo   Implement test_bbp_suppress_private_author_link().
+	 */
+	public function test_bbp_suppress_private_author_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/functions/reply.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/functions/reply.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/functions/reply.php	(revision 7162)
@@ -0,0 +1,371 @@
+<?php
+
+/**
+ * Tests for the reply component functions.
+ *
+ * @group replies
+ * @group functions
+ * @group reply
+ */
+class BBP_Tests_Replies_Functions_Reply extends BBP_UnitTestCase {
+
+	/**
+	 * @group canonical
+	 * @covers ::bbp_insert_reply
+	 */
+	public function test_bbp_insert_reply() {
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_title' => 'Reply To: Topic 1',
+			'post_content' => 'Content of reply to Topic 1',
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the reply.
+		$reply = bbp_get_reply( $r );
+
+		remove_all_filters( 'bbp_get_reply_content' );
+
+		// Reply post.
+		$this->assertSame( 'Reply To: Topic 1', bbp_get_reply_title( $r ) );
+		$this->assertSame( 'Content of reply to Topic 1', bbp_get_reply_content( $r ) );
+		$this->assertSame( 'publish', bbp_get_reply_status( $r ) );
+		$this->assertSame( $t, wp_get_post_parent_id( $r ) );
+		$this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?reply=' . $reply->post_name, $reply->guid );
+
+		// Reply meta.
+		$this->assertSame( $f, bbp_get_reply_forum_id( $r ) );
+		$this->assertSame( $t, bbp_get_reply_topic_id( $r ) );
+	}
+
+	/**
+	 * @covers ::bbp_new_reply_handler
+	 * @todo   Implement test_bbp_new_reply_handler().
+	 */
+	public function test_bbp_new_reply_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_reply_handler
+	 * @todo   Implement test_bbp_edit_reply_handler().
+	 */
+	public function test_bbp_edit_reply_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_reply
+	 * @todo   Implement test_bbp_update_reply().
+	 */
+	public function test_bbp_update_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_walker
+	 * @todo   Implement test_bbp_update_reply_walker().
+	 */
+	public function test_bbp_update_reply_walker() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_forum_id
+	 */
+	public function test_bbp_update_reply_forum_id() {
+		bbp_create_initial_content();
+
+		$forum_id = 36;
+		$topic_id = 37;
+		$reply_id = 38;
+
+		bbp_update_reply_forum_id( $reply_id, $forum_id);
+
+		$reply_forum_id = bbp_get_reply_forum_id( $reply_id );
+		$this->assertSame( 36, $reply_forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_topic_id
+	 */
+	public function test_bbp_update_reply_topic_id() {
+		bbp_create_initial_content();
+
+		$forum_id = 36;
+		$topic_id = 37;
+		$reply_id = 38;
+
+		bbp_update_reply_topic_id( $reply_id, $topic_id);
+
+		$reply_topic_id = bbp_get_reply_topic_id( $reply_id );
+		$this->assertSame( 37, $reply_topic_id );
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_to
+	 * @todo   Implement test_bbp_update_reply_to().
+	 */
+	public function test_bbp_update_reply_to() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_ancestors
+	 * @todo   Implement test_bbp_get_reply_ancestors().
+	 */
+	public function test_bbp_get_reply_ancestors() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_revision_log
+	 * @todo   Implement test_bbp_update_reply_revision_log().
+	 */
+	public function test_bbp_update_reply_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_move_reply_handler
+	 * @todo   Implement test_bbp_move_reply_handler().
+	 */
+	public function test_bbp_move_reply_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_move_reply_count
+	 * @todo   Implement test_bbp_move_reply_count().
+	 */
+	public function test_bbp_move_reply_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_toggle_reply_handler
+	 * @todo   Implement test_bbp_toggle_reply_handler().
+	 */
+	public function test_bbp_toggle_reply_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_delete_reply
+	 * @todo   Implement test_bbp_delete_reply().
+	 */
+	public function test_bbp_delete_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trash_reply
+	 * @todo   Implement test_bbp_trash_reply().
+	 */
+	public function test_bbp_trash_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrash_reply
+	 * @todo   Implement test_bbp_untrash_reply().
+	 */
+	public function test_bbp_untrash_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_deleted_reply
+	 * @todo   Implement test_bbp_deleted_reply().
+	 */
+	public function test_bbp_deleted_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trashed_reply
+	 * @todo   Implement test_bbp_trashed_reply().
+	 */
+	public function test_bbp_trashed_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrashed_reply
+	 * @todo   Implement test_bbp_untrashed_reply().
+	 */
+	public function test_bbp_untrashed_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_replies_per_page
+	 * @todo   Implement test_bbp_get_replies_per_page().
+	 */
+	public function test_bbp_get_replies_per_page() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_replies_per_rss_page
+	 * @todo   Implement test_bbp_get_replies_per_rss_page().
+	 */
+	public function test_bbp_get_replies_per_rss_page() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_content_autoembed
+	 * @todo   Implement test_bbp_reply_content_autoembed().
+	 */
+	public function test_bbp_reply_content_autoembed() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::_bbp_has_replies_where
+	 * @todo   Implement test_bbp_has_replies_where().
+	 */
+	public function test_bbp_has_replies_where() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_display_replies_feed_rss2
+	 * @todo   Implement test_bbp_display_replies_feed_rss2().
+	 */
+	public function test_bbp_display_replies_feed_rss2() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_reply_edit
+	 * @todo   Implement test_bbp_check_reply_edit().
+	 */
+	public function test_bbp_check_reply_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_reply_position
+	 * @todo   Implement test_bbp_update_reply_position().
+	 */
+	public function test_bbp_update_reply_position() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_position_raw
+	 * @todo   Implement test_bbp_get_reply_position_raw().
+	 */
+	public function test_bbp_get_reply_position_raw() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_list_replies
+	 * @todo   Implement test_bbp_list_replies().
+	 */
+	public function test_bbp_list_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_validate_reply_to
+	 * @todo   Implement test_bbp_validate_reply_to().
+	 */
+	public function test_bbp_validate_reply_to() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/functions/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/functions/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/functions/status.php	(revision 7162)
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * Tests for the reply component functions.
+ *
+ * @group replies
+ * @group functions
+ * @group status
+ */
+class BBP_Tests_Replies_Functions_Status extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_reply_statuses
+	 * @todo   Implement test_bbp_get_reply_statuses().
+	 */
+	public function test_bbp_get_reply_statuses() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_spam_reply
+	 */
+	public function test_bbp_spam_reply() {
+
+		// Create a forum
+		$f = $this->factory->forum->create();
+
+		// Create a topic
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Create some replies
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_spam_reply( $r[1] );
+
+		$reply_post_status = bbp_get_reply_status( $r[1] );
+		$this->assertSame( 'spam', $reply_post_status );
+
+		$reply_spam_meta_status = get_post_meta( $r[1], '_bbp_spam_meta_status', true );
+		$this->assertSame( 'publish', $reply_spam_meta_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '2', $topic_reply_count );
+	}
+
+	/**
+	 * @covers ::bbp_unspam_reply
+	 */
+	public function test_bbp_unspam_reply() {
+
+		// Create a forum
+		$f = $this->factory->forum->create();
+
+		// Create a topic
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Create some replies
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_spam_reply( $r[1] );
+
+		$reply_post_status = bbp_get_reply_status( $r[1] );
+		$this->assertSame( 'spam', $reply_post_status );
+
+		$reply_spam_meta_status = get_post_meta( $r[1], '_bbp_spam_meta_status', true );
+		$this->assertSame( 'publish', $reply_spam_meta_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '2', $topic_reply_count );
+
+		bbp_unspam_reply( $r[1] );
+
+		$reply_post_status = bbp_get_reply_status( $r[1] );
+		$this->assertSame( 'publish', $reply_post_status );
+
+		$reply_spam_meta_status = get_post_meta( $r[1], '_bbp_spam_meta_status', true );
+		$this->assertSame( '', $reply_spam_meta_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '3', $topic_reply_count );
+	}
+
+	/**
+	 * @covers ::bbp_approve_reply
+	 */
+	public function test_bbp_approve_reply() {
+
+		// Create a forum.
+		$f = $this->factory->forum->create();
+
+		// Create a topic.
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Create some replies.
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_post_status = bbp_get_reply_status( $r1 );
+		$this->assertSame( 'publish', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $topic_reply_count );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_status' => bbp_get_pending_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_post_status = bbp_get_reply_status( $r2 );
+		$this->assertSame( 'pending', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $topic_reply_count );
+
+		bbp_approve_reply( $r2 );
+
+		$reply_post_status = bbp_get_reply_status( $r2 );
+		$this->assertSame( 'publish', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '2', $topic_reply_count );
+	}
+
+	/**
+	 * @covers ::bbp_unapprove_reply
+	 */
+	public function test_bbp_unapprove_reply() {
+
+		// Create a forum.
+		$f = $this->factory->forum->create();
+
+		// Create a topic.
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Create some replies.
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_post_status = bbp_get_reply_status( $r1 );
+		$this->assertSame( 'publish', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $topic_reply_count );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_post_status = bbp_get_reply_status( $r2 );
+		$this->assertSame( 'publish', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '2', $topic_reply_count );
+
+		bbp_unapprove_reply( $r2 );
+
+		$reply_post_status = bbp_get_reply_status( $r2 );
+		$this->assertSame( 'pending', $reply_post_status );
+
+		$topic_reply_count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $topic_reply_count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/authors.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/authors.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/authors.php	(revision 7162)
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_reply_author_*()` template functions.
+ *
+ * @group replies
+ * @group template
+ * @group authors
+ */
+class BBP_Tests_Replies_Template_Authors extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_reply_author
+	 * @covers ::bbp_get_reply_author
+	 * @todo   Implement test_bbp_get_reply_author().
+	 */
+	public function test_bbp_get_reply_author() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_id
+	 * @covers ::bbp_get_reply_author_id
+	 */
+	public function test_bbp_get_reply_author_id() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply = bbp_get_reply_author_id( $r );
+		$this->assertSame( $u, $reply );
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_display_name
+	 * @covers ::bbp_get_reply_author_display_name
+	 */
+	public function test_bbp_get_reply_author_display_name() {
+		$u = $this->factory->user->create( array(
+			'display_name' => 'Barry B. Benson',
+		) );
+		$t = $this->factory->topic->create();
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply = bbp_get_reply_author_display_name( $r );
+		$this->assertSame( 'Barry B. Benson', $reply );
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_avatar
+	 * @covers ::bbp_get_reply_author_avatar
+	 * @todo   Implement test_bbp_get_reply_author_avatar().
+	 */
+	public function test_bbp_get_reply_author_avatar() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_link
+	 * @covers ::bbp_get_reply_author_link
+	 * @todo   Implement test_bbp_get_reply_author_link().
+	 */
+	public function test_bbp_get_reply_author_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_url
+	 * @covers ::bbp_get_reply_author_url
+	 * @todo   Implement test_bbp_get_reply_author_url().
+	 */
+	public function test_bbp_get_reply_author_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_email
+	 * @covers ::bbp_get_reply_author_email
+	 * @todo   Implement test_bbp_get_reply_author_email().
+	 */
+	public function test_bbp_get_reply_author_email() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_author_role
+	 * @covers ::bbp_get_reply_author_role
+	 * @todo   Implement test_bbp_get_reply_author_role().
+	 */
+	public function test_bbp_get_reply_author_role() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/forms.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/forms.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/forms.php	(revision 7162)
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_reply_*_()` functions.
+ *
+ * @group replies
+ * @group template
+ * @group forms
+ */
+class BBP_Tests_Replies_Template_Forms extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_form_reply_content
+	 * @covers ::bbp_get_form_reply_content
+	 * @todo   Implement test_bbp_get_form_reply_content().
+	 */
+	public function test_bbp_get_form_reply_content() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_reply_to
+	 * @covers ::bbp_get_form_reply_to
+	 * @todo   Implement test_bbp_get_form_reply_to().
+	 */
+	public function test_bbp_get_form_reply_to() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_to_dropdown
+	 * @covers ::bbp_get_reply_to_dropdown
+	 * @todo   Implement test_bbp_get_reply_to_dropdown().
+	 */
+	public function test_bbp_get_reply_to_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_reply_log_edit
+	 * @covers ::bbp_get_form_reply_log_edit
+	 * @todo   Implement test_bbp_get_form_reply_log_edit().
+	 */
+	public function test_bbp_get_form_reply_log_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_reply_edit_reason
+	 * @covers ::bbp_get_form_reply_edit_reason
+	 * @todo   Implement test_bbp_get_form_reply_edit_reason().
+	 */
+	public function test_bbp_get_form_reply_edit_reason() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_reply_status_dropdown
+	 * @covers ::bbp_get_form_reply_status_dropdown
+	 * @todo   Implement test_bbp_get_form_reply_status_dropdown().
+	 */
+	public function test_bbp_get_form_reply_status_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_form_post_request
+	 * @todo   Implement test_bbp_is_reply_form_post_request().
+	 */
+	public function test_bbp_is_reply_form_post_request() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/links.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/links.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/links.php	(revision 7162)
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_reply_*()` template functions.
+ *
+ * @group replies
+ * @group template
+ * @group links
+ */
+class BBP_Tests_Replies_Template_Links extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_reply_to_link
+	 * @covers ::bbp_get_reply_to_link
+	 * @todo   Implement test_bbp_get_reply_to_link().
+	 */
+	public function test_bbp_get_reply_to_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_cancel_reply_to_link
+	 * @covers ::bbp_get_cancel_reply_to_link
+	 * @todo   Implement test_bbp_get_cancel_reply_to_link().
+	 */
+	public function test_bbp_get_cancel_reply_to_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_admin_links
+	 * @covers ::bbp_get_reply_admin_links
+	 * @todo   Implement test_bbp_get_reply_admin_links().
+	 */
+	public function test_bbp_get_reply_admin_links() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_edit_link
+	 * @covers ::bbp_get_reply_edit_link
+	 * @todo   Implement test_bbp_get_reply_edit_link().
+	 */
+	public function test_bbp_get_reply_edit_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_edit_url
+	 * @covers ::bbp_get_reply_edit_url
+	 * @todo   Implement test_bbp_get_reply_edit_url().
+	 */
+	public function test_bbp_get_reply_edit_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_trash_link
+	 * @covers ::bbp_get_reply_trash_link
+	 * @todo   Implement test_bbp_get_reply_trash_link().
+	 */
+	public function test_bbp_get_reply_trash_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_spam_link
+	 * @covers ::bbp_get_reply_spam_link
+	 * @todo   Implement test_bbp_get_reply_spam_link().
+	 */
+	public function test_bbp_get_reply_spam_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_move_link
+	 * @covers ::bbp_get_reply_move_link
+	 * @todo   Implement test_bbp_get_reply_move_link().
+	 */
+	public function test_bbp_get_reply_move_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_split_link
+	 * @covers ::bbp_get_topic_split_link
+	 * @todo   Implement test_bbp_get_topic_split_link().
+	 */
+	public function test_bbp_get_topic_split_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_approve_link
+	 * @covers ::bbp_get_reply_approve_link
+	 * @todo   Implement test_bbp_get_reply_approve_link().
+	 */
+	public function test_bbp_get_reply_approve_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_pagination_links
+	 * @covers ::bbp_get_topic_pagination_links
+	 * @todo   Implement test_bbp_get_topic_pagination_links().
+	 */
+	public function test_bbp_get_topic_pagination_links() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/loop.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/loop.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/loop.php	(revision 7162)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Tests for the reply loop functions.
+ *
+ * @group replies
+ * @group template
+ * @group loop
+ */
+class BBP_Tests_Replies_Template_Reply_Loop extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_has_replies
+	 * @todo   Implement test_bbp_has_replies().
+	 */
+	public function test_bbp_has_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_replies
+	 * @todo   Implement test_bbp_replies().
+	 */
+	public function test_bbp_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_the_reply
+	 * @todo   Implement test_bbp_the_reply().
+	 */
+	public function test_bbp_the_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/post_type.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/post_type.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/post_type.php	(revision 7162)
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_reply_post_type_*()` functions.
+ *
+ * @group replies
+ * @group template
+ * @group post_type
+ */
+class BBP_Tests_Replies_Template_Post_Type extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_reply_post_type
+	 * @covers ::bbp_get_reply_post_type
+	 */
+	public function test_bbp_reply_post_type() {
+		$t = $this->factory->topic->create();
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$robj = get_post_type_object( 'reply' );
+
+		// WordPress 4.6 introduced `WP_Post_Type` class
+		if ( bbp_get_major_wp_version() < 4.6 ) {
+			$this->assertInstanceOf( 'stdClass', $robj );
+		} else {
+			$this->assertInstanceOf( 'WP_Post_Type', $robj );
+		}
+
+		$this->assertEquals( 'reply', $robj->name );
+
+		// Test some defaults
+		$this->assertFalse( is_post_type_hierarchical( 'topic' ) );
+		$reply_type = bbp_reply_post_type( $r );
+		$this->expectOutputString( 'reply', $reply_type );
+
+		$reply_type = bbp_get_reply_post_type( $r );
+		$this->assertSame( 'reply', $reply_type );
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_post_type_labels
+	 * @todo   Implement test_bbp_get_reply_post_type_labels().
+	 */
+	public function test_bbp_get_reply_post_type_labels() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_post_type_rewrite
+	 * @todo   Implement test_bbp_get_reply_post_type_rewrite().
+	 */
+	public function test_bbp_get_reply_post_type_rewrite() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_post_type_supports
+	 * @todo   Implement test_bbp_get_reply_post_type_supports().
+	 */
+	public function test_bbp_get_reply_post_type_supports() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/reply.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/reply.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/reply.php	(revision 7162)
@@ -0,0 +1,597 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_reply_*()` template functions.
+ *
+ * @group replies
+ * @group template
+ * @group reply
+ */
+class BBP_Tests_Replies_Template_Reply extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_reply_id
+	 * @covers ::bbp_get_reply_id
+	 */
+	public function test_bbp_get_reply_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_id = bbp_get_reply_id( $r );
+		$this->assertSame( $r, $reply_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_reply
+	 * @todo   Implement test_bbp_get_reply().
+	 */
+	public function test_bbp_get_reply() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_permalink
+	 * @covers ::bbp_get_reply_permalink
+	 */
+	public function test_bbp_get_reply_permalink() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_permalink = bbp_get_reply_permalink( $r );
+
+		$this->expectOutputString( $reply_permalink );
+		bbp_reply_permalink( $r );
+
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?reply=' . bbp_get_reply_id( $r ), $reply_permalink );
+	}
+
+	/**
+	 * @covers ::bbp_reply_url
+	 * @covers ::bbp_get_reply_url
+	 *
+	 * @ticket BBP2845
+	 */
+	public function test_bbp_get_reply_url() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multisite for now.' );
+		}
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create_many( 7, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Store the original reply pagination option value.
+		$default_reply_page = bbp_get_replies_per_page();
+
+		// Lower the reply pagination value to test without so many replies.
+		update_option( '_bbp_replies_per_page', 3 );
+
+		// Reply menu position is unaltered when bbp_show_lead_topic() true.
+		add_filter( 'bbp_show_lead_topic', '__return_true' );
+
+		// 1st reply is on the first page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '#post-' . bbp_get_reply_id( $r[0] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[0] ) );
+
+		// 2nd reply is on the first page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '#post-' . bbp_get_reply_id( $r[1] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[1] ) );
+
+		// 3rd reply is on the first page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '#post-' . bbp_get_reply_id( $r[2] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[2] ) );
+
+		// 4th reply is on the second page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[3] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[3] ) );
+
+		// 5th reply is on the second page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[4] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[4] ) );
+
+		// 6th reply is on the second page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[5] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[5] ) );
+
+		// 7th reply is on the third page, 3 replies and 1 topic per page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=3#post-' . bbp_get_reply_id( $r[6] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[6] ) );
+
+		// Remove the filter for WordPress < 4.0 compatibility.
+		remove_filter( 'bbp_show_lead_topic', '__return_true' );
+
+		// Reply menu position is bumped by 1 when bbp_show_lead_topic() false.
+		add_filter( 'bbp_show_lead_topic', '__return_false' );
+
+		// 1st reply is on the first page, 2 replies and 1 topic per first page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '#post-' . bbp_get_reply_id( $r[0] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[0] ) );
+
+		// 2nd reply is on the first page, 2 replies and 1 topic per first page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '#post-' . bbp_get_reply_id( $r[1] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[1] ) );
+
+		// 3rd reply is on the second page, 2 replies and 1 topic per first page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[2] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[2] ) );
+
+		// 4th reply is on the second page, 3 replies per subsequent page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[3] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[3] ) );
+
+		// 5th reply is on the second page, 3 replies per subsequent page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=2#post-' . bbp_get_reply_id( $r[4] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[4] ) );
+
+		// 6th reply is on the third page, 3 replies per subsequent page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=3#post-' . bbp_get_reply_id( $r[5] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[5] ) );
+
+		// 7th reply is on the third page, 3 replies per subsequent page.
+		$reply_url = bbp_get_topic_permalink( $t ) . '&paged=3#post-' . bbp_get_reply_id( $r[6] );
+		$this->assertSame( $reply_url, bbp_get_reply_url( $r[6] ) );
+
+		// Remove the filter for WordPress < 4.0 compatibility.
+		remove_filter( 'bbp_show_lead_topic', '__return_false' );
+
+		// Restore the original reply pagination option value.
+		update_option( '_bbp_replies_per_page', $default_reply_page );
+	}
+
+	/**
+	 * @covers ::bbp_reply_title
+	 * @covers ::bbp_get_reply_title
+	 */
+	public function test_bbp_get_reply_title() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_title = bbp_get_reply_title( $r );
+		$this->assertSame( 'Reply To: ' . bbp_get_topic_title( $t ), $reply_title );
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_title_fallback
+	 */
+	public function test_bbp_get_reply_title_fallback() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_title'  => 'What are you supposed to be, some kind of a cosmonaut?',
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_title = 'What are you supposed to be, some kind of a cosmonaut?';
+		$reply_title_fallback = bbp_get_reply_title_fallback( $reply_title, $r );
+		$this->assertSame( $reply_title , $reply_title_fallback );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_title = '';
+		$reply_title_fallback = bbp_get_reply_title_fallback( $reply_title, $r );
+		$this->assertSame( 'Reply To: ' . bbp_get_topic_title( $t ), $reply_title_fallback );
+	}
+
+	/**
+	 * @covers ::bbp_reply_content
+	 * @covers ::bbp_get_reply_content
+	 */
+	public function test_bbp_get_reply_content() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_content' => 'Content of Reply 1',
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		remove_all_filters( 'bbp_get_reply_content' );
+		$reply_content = bbp_get_reply_content( $r );
+		$this->assertSame( 'Content of Reply 1', $reply_content );
+	}
+
+	/**
+	 * @covers ::bbp_reply_excerpt
+	 * @covers ::bbp_get_reply_excerpt
+	 */
+	public function test_bbp_get_reply_excerpt() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent'   => $t,
+			'post_content'  => 'I feel like the floor of a taxi cab.',
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		remove_all_filters( 'bbp_get_reply_content' );
+		$reply_excerpt = bbp_get_reply_excerpt( $r, 22 );
+		$this->assertSame( 'I feel like the floor&hellip;', $reply_excerpt );
+	}
+
+	/**
+	 * @covers ::bbp_reply_post_date
+	 * @covers ::bbp_get_reply_post_date
+	 */
+	public function test_bbp_get_reply_post_date() {
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60 * 66 );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Configue our written date time, August 4, 2012 at 2:37 pm.
+		$gmt = false;
+		$date   = get_post_time( get_option( 'date_format' ), $gmt, $r, true );
+		$time   = get_post_time( get_option( 'time_format' ), $gmt, $r, true );
+		$result = sprintf( '%1$s at %2$s', $date, $time );
+
+		// Output, string, August 4, 2012 at 2:37 pm.
+		$this->expectOutputString( $result );
+		bbp_reply_post_date( $r );
+
+		// String, August 4, 2012 at 2:37 pm.
+		$datetime = bbp_get_topic_post_date( $r, false, false );
+		$this->assertSame( $result, $datetime );
+
+		// Humanized string, 4 days, 4 hours ago.
+		$datetime = bbp_get_topic_post_date( $r, true, false );
+		$this->assertSame( '1 hour, 6 minutes ago', $datetime );
+
+		// Humanized string using GMT formatted date, 4 days, 4 hours ago.
+		$datetime = bbp_get_topic_post_date( $r, true, true );
+		$this->assertSame( '1 hour, 6 minutes ago', $datetime );
+	}
+
+	/**
+	 * @covers ::bbp_reply_topic_title
+	 * @covers ::bbp_get_reply_topic_title
+	 */
+	public function test_bbp_get_reply_topic_title() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_topic_title = bbp_get_reply_topic_title( $r );
+		$this->assertSame( bbp_get_topic_title( $t ), $reply_topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_reply_topic_id
+	 * @covers ::bbp_get_reply_topic_id
+	 */
+	public function test_bbp_get_reply_topic_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_topic_id = bbp_get_reply_topic_id( $r );
+		$this->assertSame( $t, $reply_topic_id );
+	}
+
+	/**
+	 * @covers ::bbp_reply_forum_id
+	 * @covers ::bbp_get_reply_forum_id
+	 */
+	public function test_bbp_get_reply_forum_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$reply_forum_id = bbp_get_reply_forum_id( $r );
+		$this->assertSame( $f, $reply_forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_reply_ancestor_id
+	 * @covers ::bbp_get_reply_ancestor_id
+	 */
+	public function test_bbp_get_reply_ancestor_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+				'reply_to' => $r1,
+			),
+		) );
+
+		$reply_ancestor_id = bbp_get_reply_ancestor_id( $r2 );
+		$this->assertSame( $r1, $reply_ancestor_id );
+	}
+
+	/**
+	 * @covers ::bbp_reply_to
+	 * @covers ::bbp_get_reply_to
+	 */
+	public function test_bbp_get_reply_to() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+				'reply_to' => $r1,
+			),
+		) );
+
+		$reply_to = bbp_get_reply_to( $r2 );
+		$this->assertSame( $r1, $reply_to );
+	}
+
+	/**
+	 * @covers ::bbp_reply_position
+	 * @covers ::bbp_get_reply_position
+	 *
+	 * @ticket BBP2845
+	 */
+	public function test_bbp_get_reply_position() {
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create_many( 7, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Reply menu position is unaltered when bbp_show_lead_topic() true.
+		add_filter( 'bbp_show_lead_topic', '__return_true' );
+
+		$position = get_post_field( 'menu_order', $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position_raw( $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position( $r[3] );
+		$this->assertSame( 4, $position );
+
+		// Force a reply's 'menu_order' to 0.
+		bbp_update_reply_position( $r[3], 0 );
+
+		$position = get_post_field( 'menu_order', $r[3] );
+		$this->assertSame( 0, $position );
+
+		$position = bbp_get_reply_position_raw( $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position( $r[3] );
+		$this->assertSame( 4, $position );
+
+		// Remove the filter for WordPress < 4.0 compatibility.
+		remove_filter( 'bbp_show_lead_topic', '__return_true' );
+
+		// Reply menu position is bumped by 1 when bbp_show_lead_topic() false.
+		add_filter( 'bbp_show_lead_topic', '__return_false' );
+
+		$position = get_post_field( 'menu_order', $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position_raw( $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position( $r[3] );
+		$this->assertSame( 5, $position );
+
+		// Force a reply's 'menu_order' to 0.
+		bbp_update_reply_position( $r[3], 0 );
+
+		$position = get_post_field( 'menu_order', $r[3] );
+		$this->assertSame( 0, $position );
+
+		$position = bbp_get_reply_position_raw( $r[3] );
+		$this->assertSame( 4, $position );
+
+		$position = bbp_get_reply_position( $r[3] );
+		$this->assertSame( 5, $position );
+
+		// Remove the filter for WordPress < 4.0 compatibility.
+		remove_filter( 'bbp_show_lead_topic', '__return_false' );
+	}
+
+	/**
+	 * @covers ::bbp_reply_class
+	 * @covers ::bbp_get_reply_class
+	 * @todo   Implement test_bbp_get_reply_class().
+	 */
+	public function test_bbp_get_reply_class() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_pagination_count
+	 * @covers ::bbp_get_topic_pagination_count
+	 * @todo   Implement test_bbp_get_topic_pagination_count().
+	 */
+	public function test_bbp_get_topic_pagination_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/revisions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/revisions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/revisions.php	(revision 7162)
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Tests for the reply revision template functions.
+ *
+ * @group replies
+ * @group template
+ * @group revisions
+ */
+class BBP_Tests_Replies_Template_Revisions extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_reply_content_append_revisions
+	 * @todo   Implement test_bbp_reply_content_append_revisions().
+	 */
+	public function test_bbp_reply_content_append_revisions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_reply_revision_log
+	 * @covers ::bbp_get_reply_revision_log
+	 * @todo   Implement test_bbp_get_reply_revision_log().
+	 */
+	public function test_bbp_get_reply_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_raw_revision_log
+	 * @todo   Implement test_bbp_get_reply_raw_revision_log().
+	 */
+	public function test_bbp_get_reply_raw_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_revisions
+	 * @todo   Implement test_bbp_get_reply_revisions().
+	 */
+	public function test_bbp_get_reply_revisions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_reply_revision_count
+	 * @todo   Implement test_bbp_get_reply_revision_count().
+	 */
+	public function test_bbp_get_reply_revision_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/replies/template/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/replies/template/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/replies/template/status.php	(revision 7162)
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * Tests for the reply status template functions.
+ *
+ * @group replies
+ * @group template
+ * @group status
+ */
+class BBP_Tests_Repliess_Template_Status extends BBP_UnitTestCase {
+	protected $old_current_user = 0;
+
+	public function setUp() {
+		parent::setUp();
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
+		$this->keymaster_id = get_current_user_id();
+		bbp_set_user_role( $this->keymaster_id, bbp_get_keymaster_role() );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_reply_status
+	 * @covers ::bbp_get_reply_status
+	 * @todo   Implement test_bbp_get_reply_status().
+	 */
+	public function test_bbp_get_reply_status() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_published
+	 */
+	public function test_bbp_is_reply_published() {
+		$forum_id = $this->factory->forum->create();
+
+		$topic_id = $this->factory->topic->create( array(
+			'post_parent' => $forum_id,
+			'topic_meta' => array(
+				'forum_id' => $forum_id,
+			),
+		) );
+
+		$reply_id = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id' => $forum_id,
+				'topic_id' => $topic_id,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id'              => $forum_id,
+				'topic_id'              => $topic_id,
+			)
+		) );
+
+		$reply_published = bbp_is_reply_published( $r );
+		$this->assertTrue( $reply_published );
+		$reply_published = bbp_is_reply_published( $reply_id );
+		$this->assertTrue( $reply_published );
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_spam
+	 */
+	public function test_bbp_is_reply_spam() {
+		$forum_id = $this->factory->forum->create();
+
+		$topic_id = $this->factory->topic->create( array(
+			'post_parent' => $forum_id,
+			'topic_meta' => array(
+				'forum_id' => $forum_id,
+			),
+		) );
+
+		$reply_id = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id' => $forum_id,
+				'topic_id' => $topic_id,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id'              => $forum_id,
+				'topic_id'              => $topic_id,
+			)
+		) );
+
+		bbp_spam_reply( $r );
+
+		$reply_spam = bbp_is_reply_spam( $r );
+		$this->assertTrue( $reply_spam );
+
+		bbp_unspam_reply( $r );
+
+		$reply_spam = bbp_is_reply_spam( $r );
+		$this->assertFalse( $reply_spam );
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_trash
+	 * @todo   Implement test_bbp_is_reply_trash().
+	 */
+	public function test_bbp_is_reply_trash() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_pending
+	 */
+	public function test_bbp_is_reply_pending() {
+		$forum_id = $this->factory->forum->create();
+
+		$topic_id = $this->factory->topic->create( array(
+			'post_parent' => $forum_id,
+			'topic_meta' => array(
+				'forum_id' => $forum_id,
+			),
+		) );
+
+		$reply_id = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id' => $forum_id,
+				'topic_id' => $topic_id,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id'              => $forum_id,
+				'topic_id'              => $topic_id,
+			)
+		) );
+
+		bbp_unapprove_reply( $r );
+
+		$reply_pending = bbp_is_reply_pending( $r );
+		$this->assertTrue( $reply_pending );
+
+		bbp_approve_reply( $r );
+
+		$reply_pending = bbp_is_reply_pending( $r );
+		$this->assertFalse( $reply_pending );
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_private
+	 */
+	public function test_bbp_is_reply_private() {
+		$forum_id = $this->factory->forum->create();
+
+		$topic_id = $this->factory->topic->create( array(
+			'post_parent' => $forum_id,
+			'topic_meta' => array(
+				'forum_id' => $forum_id,
+			),
+		) );
+
+		$reply_id = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id' => $forum_id,
+				'topic_id' => $topic_id,
+			),
+		) );
+
+		$reply_private = bbp_is_reply_private( $reply_id );
+		$this->assertFalse( $reply_private );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'post_status' => bbp_get_private_status_id(),
+			'reply_meta' => array(
+				'forum_id'              => $forum_id,
+				'topic_id'              => $topic_id,
+			)
+		) );
+
+		$reply_private = bbp_is_reply_private( $r );
+		$this->assertTrue( $reply_private );
+	}
+
+	/**
+	 * @covers ::bbp_is_reply_anonymous
+	 * @todo   Implement test_bbp_is_reply_anonymous().
+	 */
+	public function test_bbp_is_reply_anonymous() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/search/functions/search.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/search/functions/search.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/search/functions/search.php	(revision 7162)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Tests for the search component search functions.
+ *
+ * @group search
+ * @group functions
+ */
+class BBP_Tests_Search_Functions_Search extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_search_query
+	 * @todo   Implement test_bbp_search_query().
+	 */
+	public function test_bbp_search_query() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_search_query_args
+	 * @todo   Implement test_bbp_get_search_query_args().
+	 */
+	public function test_bbp_get_search_query_args() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_results_redirect
+	 * @todo   Implement test_bbp_search_results_redirect().
+	 */
+	public function test_bbp_search_results_redirect() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/search/template/search.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/search/template/search.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/search/template/search.php	(revision 7162)
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * Tests for the search component search template functions.
+ *
+ * @group search
+ * @group template
+ */
+class BBP_Tests_Search_Template_Search extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_has_search_results
+	 * @todo   Implement test_bbp_has_search_results().
+	 */
+	public function test_bbp_has_search_results() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_results
+	 * @todo   Implement test_bbp_search_results().
+	 */
+	public function test_bbp_search_results() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_the_search_result
+	 * @todo   Implement test_bbp_the_search_result().
+	 */
+	public function test_bbp_the_search_result() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_title
+	 * @covers ::bbp_get_search_title
+	 * @todo   Implement test_bbp_get_search_title().
+	 */
+	public function test_bbp_get_search_title() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_url
+	 * @covers ::bbp_get_search_url
+	 * @todo   Implement test_bbp_get_search_url().
+	 */
+	public function test_bbp_get_search_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_results_url
+	 * @covers ::bbp_get_search_results_url
+	 * @todo   Implement test_bbp_get_search_results_url().
+	 */
+	public function test_bbp_get_search_results_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_terms
+	 * @covers ::bbp_get_search_terms
+	 * @todo   Implement test_bbp_get_search_terms().
+	 */
+	public function test_bbp_get_search_terms() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_pagination_count
+	 * @covers ::bbp_get_search_pagination_count
+	 * @todo   Implement test_bbp_get_search_pagination_count().
+	 */
+	public function test_bbp_get_search_pagination_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_search_pagination_links
+	 * @covers ::bbp_get_search_pagination_links
+	 * @todo   Implement test_bbp_get_search_pagination_links().
+	 */
+	public function test_bbp_get_search_pagination_links() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/functions/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/functions/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/functions/counts.php	(revision 7162)
@@ -0,0 +1,588 @@
+<?php
+
+/**
+ * Tests for the topic component count functions.
+ *
+ * @group topics
+ * @group functions
+ * @group counts
+ */
+class BBP_Tests_Topics_Functions_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * Generic function to test the topics counts with a new reply
+	 */
+	public function test_bbp_topic_new_reply_counts() {
+		remove_action( 'bbp_insert_reply', 'bbp_insert_reply_update_counts', 10 );
+
+		$u  = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$f  = $this->factory->forum->create();
+		$t  = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Don't attempt to send an email. This is for speed and PHP errors.
+		remove_action( 'bbp_new_reply', 'bbp_notify_topic_subscribers', 11, 5 );
+
+		// Simulate the 'bbp_new_reply' action.
+		do_action( 'bbp_new_reply', $r1, $t, $f, false, bbp_get_current_user_id() );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Simulate the 'bbp_new_topic' action.
+		do_action( 'bbp_new_reply', $r2, $t, $f, false, $u2 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 2, $count );
+
+		// Re-add removed actions.
+		add_action( 'bbp_insert_reply', 'bbp_insert_reply_update_counts', 10, 2 );
+		add_action( 'bbp_new_reply',    'bbp_notify_topic_subscribers',   11, 5 );
+	}
+
+	/**
+	 * Generic function to test the topic counts on a deleted reply
+	 */
+	public function test_bbp_topic_deleted_reply_counts() {
+		$u  = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$f  = $this->factory->forum->create();
+		$t  = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_update_topic_reply_count( $t );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_update_topic_reply_count_hidden( $t );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 1, $count );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_update_topic_reply_count( $t );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_update_topic_reply_count_hidden( $t );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 2, $count );
+
+		// ToDo: Update this to use bbp_delete_reply().
+		clean_post_cache( $t );
+		wp_delete_post( $r2, true );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 1, $count );
+	}
+
+	/**
+	 * Generic function to test the topic counts on a trashed/untrashed reply
+	 */
+	public function test_bbp_topic_trashed_untrashed_reply_counts() {
+		$u  = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$f  = $this->factory->forum->create();
+		$t  = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+		$r3 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_update_topic_reply_count( $t );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_update_topic_reply_count_hidden( $t );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 2, $count );
+
+		// ToDo: Update this to use bbp_trash_reply().
+		wp_trash_post( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		// ToDo: Update this to use bbp_untrash_reply().
+		wp_untrash_post( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * Generic function to test the topic counts on a spammed/unspammed reply
+	 */
+	public function test_bbp_topic_spammed_unspammed_reply_counts() {
+		$u  = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$f  = $this->factory->forum->create();
+		$t  = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+		$r3 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_update_topic_reply_count( $t );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_update_topic_reply_count_hidden( $t );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 2, $count );
+
+		bbp_spam_reply( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		bbp_unspam_reply( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * Generic function to test the topic counts on a approved/unapproved reply
+	 */
+	public function test_bbp_topic_approved_unapproved_reply_counts() {
+		$u  = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$f  = $this->factory->forum->create();
+		$t  = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+		$r3 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_update_topic_reply_count( $t );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_update_topic_reply_count_hidden( $t );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 2, $count );
+
+		bbp_unapprove_reply( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 1, $count );
+
+		bbp_approve_reply( $r3 );
+
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( 3, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_topic_reply_count
+	 */
+	public function test_bbp_bump_topic_reply_count() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '0', $count );
+
+		$count = bbp_bump_topic_reply_count( $t );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_bump_topic_reply_count( $t, 3 );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '4', $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_topic_reply_count
+	 */
+	public function test_bbp_increase_topic_reply_count() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '0', $count );
+
+		bbp_increase_topic_reply_count( $t );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_topic_reply_count
+	 */
+	public function test_bbp_decrease_topic_reply_count() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '0', $count );
+
+		// Set the count manually to 2
+		bbp_update_topic_reply_count( $t, 2 );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '2', $count );
+
+		bbp_decrease_topic_reply_count( $t );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_topic_reply_count_hidden
+	 */
+	public function test_bbp_bump_topic_reply_count_hidden() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '0', $count );
+
+		$count = bbp_bump_topic_reply_count_hidden( $t );
+		$this->assertSame( 1, $count );
+
+		bbp_bump_topic_reply_count_hidden( $t, 3 );
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '4', $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_topic_reply_count_hidden
+	 */
+	public function test_bbp_increase_topic_reply_count_hidden() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '0', $count );
+
+		bbp_increase_topic_reply_count_hidden( $t );
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_topic_reply_count_hidden
+	 */
+	public function test_bbp_decrease_topic_reply_count_hidden() {
+		$t = $this->factory->topic->create();
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '0', $count );
+
+		// Set the count manually to 2
+		bbp_update_topic_reply_count_hidden( $t, 2 );
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '2', $count );
+
+		bbp_decrease_topic_reply_count_hidden( $t );
+
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '1', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_reply_count
+	 */
+	public function test_bbp_update_topic_reply_count() {
+		// Create a forum
+		$f = $this->factory->forum->create();
+
+		// Create a topic
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Start with zero
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '0', $count );
+
+		// Create 3 replies
+		$r1 = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '3', $count );
+
+		bbp_update_topic_reply_count( $t );
+
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '3', $count );
+
+		// Create another reply
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		// Test update using reply id
+		bbp_update_topic_reply_count( $r2 );
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '4', $count );
+
+		// Spam a reply
+		bbp_spam_reply( $r2 );
+
+		bbp_update_topic_reply_count( $t );
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '3', $count );
+
+		// Set the reply count manually
+		bbp_update_topic_reply_count( $t, 7 );
+		$count = bbp_get_topic_reply_count( $t );
+		$this->assertSame( '7', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_reply_count_hidden
+	 */
+	public function test_bbp_update_topic_reply_count_hidden() {
+		// Create a forum
+		$f = $this->factory->forum->create();
+
+		// Create a topic
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		// Start with zero
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '0', $count );
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_update_topic_reply_count_hidden( $t );
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '0', $count );
+
+		bbp_spam_reply( $r[2] );
+
+		bbp_update_topic_reply_count_hidden( $t );
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '1', $count );
+
+		bbp_unapprove_reply( $r[0] );
+
+		bbp_update_topic_reply_count_hidden( $t );
+		$count = bbp_get_topic_reply_count_hidden( $t );
+		$this->assertSame( '2', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_voice_count
+	 */
+	public function test_bbp_update_topic_voice_count() {
+		$u = $this->factory->user->create_many( 2 );
+		$t = $this->factory->topic->create();
+
+		bbp_update_topic_voice_count( $t );
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '0', $count );
+
+		$r = $this->factory->reply->create( array(
+			'post_author' => $u[0],
+			'post_parent' => $t,
+		) );
+
+		bbp_update_topic_voice_count( $t );
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '1', $count );
+
+		$r = $this->factory->reply->create( array(
+			'post_author' => $u[1],
+			'post_parent' => $t,
+		) );
+
+		bbp_update_topic_voice_count( $t );
+		$count = bbp_get_topic_voice_count( $t );
+		$this->assertSame( '2', $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_anonymous_reply_count
+	 * @todo   Implement test_bbp_update_topic_anonymous_reply_count().
+	 */
+	public function test_bbp_update_topic_anonymous_reply_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/functions/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/functions/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/functions/status.php	(revision 7162)
@@ -0,0 +1,422 @@
+<?php
+
+/**
+ * Tests for the topic component statuses and types functions.
+ *
+ * @group topics
+ * @group functions
+ * @group status
+ */
+class BBP_Tests_Topics_Functions_Status extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_topic_statuses
+	 * @todo   Implement test_bbp_get_topic_statuses().
+	 */
+	public function test_bbp_get_topic_statuses() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_types
+	 * @todo   Implement test_bbp_get_topic_types().
+	 */
+	public function test_bbp_get_topic_types() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_stickies
+	 * @todo   Implement test_bbp_get_stickies().
+	 */
+	public function test_bbp_get_stickies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_super_stickies
+	 * @todo   Implement test_bbp_get_super_stickies().
+	 */
+	public function test_bbp_get_super_stickies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_close_topic
+	 * @todo   Implement test_bbp_close_topic().
+	 */
+	public function test_bbp_close_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_open_topic
+	 * @todo   Implement test_bbp_open_topic().
+	 */
+	public function test_bbp_open_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_spam_topic
+	 */
+	public function test_bbp_spam_topic() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date_topic = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_reply = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+
+		$topic_time = '4 days, 4 hours ago';
+		$reply_time = '3 days, 8 hours ago';
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date_topic,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_date' => $post_date_reply,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_spam_topic( $t );
+
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		$last_topic_id = bbp_get_forum_last_topic_id( $f );
+		$this->assertSame( $t, $last_topic_id );
+
+		$last_reply_id = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $t, $last_reply_id );
+
+		$last_active_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $t, $last_active_id );
+
+		$last_active_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( $topic_time, $last_active_time );
+
+		$count = bbp_get_topic_reply_count( $t, true, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true, true );
+		$this->assertSame( 2, $count );
+
+		// ToDo: Result should be 0 when a topic has no replies
+	//	$last_reply_id = bbp_get_topic_last_reply_id( $t );
+	//	$this->assertSame( $t, $last_reply_id );
+
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $t, $last_active_id );
+
+		$last_active_time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $topic_time, $last_active_time );
+
+		$topic_spam_status = get_post_status( $t );
+		$this->assertSame( bbp_get_spam_status_id(), $topic_spam_status );
+
+		$topic_meta_pre_spammed_replies = get_post_meta( $t, '_bbp_pre_spammed_replies', true );
+		$this->assertEqualSets( array( $r[1], $r[0] ), $topic_meta_pre_spammed_replies );
+
+		$topic_spam_meta_status = get_post_meta( $t, '_bbp_spam_meta_status', true );
+		$this->assertSame( bbp_get_public_status_id(), $topic_spam_meta_status );
+	}
+
+	/**
+	 * @covers ::bbp_spam_topic_replies
+	 */
+	public function test_bbp_spam_topic_replies() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date_topic = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_reply = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+
+		$topic_time = '4 days, 4 hours ago';
+		$reply_time = '3 days, 8 hours ago';
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date_topic,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_date' => $post_date_reply,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_spam_topic_replies( $t );
+
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 0, $count );
+
+		$last_reply_id = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $t, $last_reply_id );
+
+		$last_active_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $t, $last_active_id );
+
+		// ToDo: Result should be topic time when a topic has no replies
+	//	$last_active_time = bbp_get_forum_last_active_time( $f );
+	//	$this->assertSame( $topic_time, $last_active_time );
+
+		$count = bbp_get_topic_reply_count( $t, true, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true, true );
+		$this->assertSame( 2, $count );
+
+		// ToDo: Result should be 0 when a topic has no replies
+	//	$last_reply_id = bbp_get_topic_last_reply_id( $t );
+	//	$this->assertSame( $t, $last_reply_id );
+
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $t, $last_active_id );
+
+		$last_active_time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $topic_time, $last_active_time );
+
+		$topic_meta_pre_spammed_replies = get_post_meta( $t, '_bbp_pre_spammed_replies', true );
+		$this->assertEqualSets( array( $r[1], $r[0] ), $topic_meta_pre_spammed_replies );
+
+		foreach ( $r as $reply ) {
+			$reply_status = get_post_status( $reply );
+			$this->assertSame( bbp_get_trash_status_id(), $reply_status );
+
+			$reply_meta_status = get_post_meta( $reply, '_wp_trash_meta_status', true );
+			$this->assertSame( bbp_get_public_status_id(), $reply_meta_status );
+		}
+	}
+
+	/**
+	 * @covers ::bbp_unspam_topic
+	 */
+	public function test_bbp_unspam_topic() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date_topic = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_reply = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+
+		$topic_time = '4 days, 4 hours ago';
+		$reply_time = '3 days, 8 hours ago';
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date_topic,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_date' => $post_date_reply,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_spam_topic( $t );
+
+		bbp_unspam_topic( $t );
+
+		$topic_status = get_post_status( $t );
+		$this->assertSame( bbp_get_public_status_id(), $topic_status );
+
+		$this->assertEquals( '', get_post_meta( $t, '_bbp_pre_spammed_replies', true ) );
+		$this->assertEquals( array(), get_post_meta( $t, '_bbp_pre_spammed_replies', false ) );
+
+		$this->assertEquals( '', get_post_meta( $t, '_bbp_spam_meta_status', true ) );
+		$this->assertEquals( array(), get_post_meta( $t, '_bbp_spam_meta_status', false ) );
+
+		$count = bbp_get_forum_topic_count( $f, false, true );
+		$this->assertSame( 1, $count );
+
+		$count = bbp_get_forum_topic_count_hidden( $f, true );
+		$this->assertSame( 0, $count );
+
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		$last_topic_id = bbp_get_forum_last_topic_id( $f );
+		$this->assertSame( $t, $last_topic_id );
+
+		$last_reply_id = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $r[1], $last_reply_id );
+
+		$last_active_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r[1], $last_active_id );
+
+		$last_active_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( $reply_time, $last_active_time );
+
+		$count = bbp_get_topic_reply_count( $t, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true, true );
+		$this->assertSame( 0, $count );
+
+		$last_reply_id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( $r[1], $last_reply_id );
+
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r[1], $last_active_id );
+
+		$last_active_time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $reply_time, $last_active_time );
+	}
+
+	/**
+	 * @covers ::bbp_unspam_topic_replies
+	 */
+	public function test_bbp_unspam_topic_replies() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date_topic = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_reply = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+
+		$topic_time = '4 days, 4 hours ago';
+		$reply_time = '3 days, 8 hours ago';
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date_topic,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'post_date' => $post_date_reply,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_spam_topic_replies( $t );
+
+		bbp_unspam_topic_replies( $t );
+
+		$this->assertEquals( '', get_post_meta( $t, '_bbp_pre_spammed_replies', true ) );
+		$this->assertEquals( array(), get_post_meta( $t, '_bbp_pre_spammed_replies', false ) );
+
+		foreach ( $r as $reply ) {
+			$reply_status = get_post_status( $reply );
+			$this->assertSame( bbp_get_public_status_id(), $reply_status );
+
+			$this->assertEquals( '', get_post_meta( $reply, '_wp_trash_meta_status', true ) );
+			$this->assertEquals( array(), get_post_meta( $reply, '_wp_trash_meta_status', false ) );
+		}
+
+		$count = bbp_get_forum_reply_count( $f, false, true );
+		$this->assertSame( 2, $count );
+
+		$last_reply_id = bbp_get_forum_last_reply_id( $f );
+		$this->assertSame( $r[1], $last_reply_id );
+
+		$last_active_id = bbp_get_forum_last_active_id( $f );
+		$this->assertSame( $r[1], $last_active_id );
+
+		$last_active_time = bbp_get_forum_last_active_time( $f );
+		$this->assertSame( $reply_time, $last_active_time );
+
+		$count = bbp_get_topic_reply_count( $t, true, true );
+		$this->assertSame( 2, $count );
+
+		$count = bbp_get_topic_reply_count_hidden( $t, true, true );
+		$this->assertSame( 0, $count );
+
+		$last_reply_id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( $r[1], $last_reply_id );
+
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r[1], $last_active_id );
+
+		$last_active_time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $reply_time, $last_active_time );
+	}
+
+	/**
+	 * @covers ::bbp_stick_topic
+	 * @todo   Implement test_bbp_stick_topic().
+	 */
+	public function test_bbp_stick_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_approve_topic
+	 * @todo   Implement test_bbp_approve_topic().
+	 */
+	public function test_bbp_approve_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_unapprove_topic
+	 * @todo   Implement test_bbp_unapprove_topic().
+	 */
+	public function test_bbp_unapprove_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_unstick_topic
+	 * @todo   Implement test_bbp_unstick_topic().
+	 */
+	public function test_bbp_unstick_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic-tag.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic-tag.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic-tag.php	(revision 7162)
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Tests for the topic component functions.
+ *
+ * @group topics
+ * @group topic_tags
+ * @group functions
+ * @group topic
+ * @group topic_tag
+ */
+class BBP_Tests_Topics_Functions_Topic_Tag extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_edit_topic_tag_handler
+	 * @todo   Implement test_bbp_edit_topic_tag_handler().
+	 */
+	public function test_bbp_edit_topic_tag_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_spam_topic_tags
+	 * @todo   Implement test_bbp_spam_topic_tags().
+	 */
+	public function test_bbp_spam_topic_tags() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_unspam_topic_tags
+	 * @todo   Implement test_bbp_unspam_topic_tags().
+	 */
+	public function test_bbp_unspam_topic_tags() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_tag_names
+	 * @todo   Implement test_bbp_get_topic_tag_names().
+	 */
+	public function test_bbp_get_topic_tag_names() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_topic_tag_edit
+	 * @todo   Implement test_bbp_check_topic_tag_edit().
+	 */
+	public function test_bbp_check_topic_tag_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/functions/topic.php	(revision 7162)
@@ -0,0 +1,533 @@
+<?php
+
+/**
+ * Tests for the topic component functions.
+ *
+ * @group topics
+ * @group functions
+ * @group topic
+ */
+class BBP_Tests_Topics_Functions_Topic extends BBP_UnitTestCase {
+
+	/**
+	 * @group canonical
+	 * @covers ::bbp_insert_topic
+	 */
+	public function test_bbp_insert_topic() {
+		$u = $this->factory->user->create_many( 2 );
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+
+		$t = $this->factory->topic->create( array(
+			'post_title'   => 'Topic 1',
+			'post_content' => 'Content for Topic 1',
+			'post_parent'  => $f,
+			'post_date'    => $post_date,
+			'post_author'  => $u[0],
+			'topic_meta'   => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date'   => $post_date,
+			'post_author'  => $u[1],
+			'reply_meta'  => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Get the topic.
+		$topic = bbp_get_topic( $t );
+
+		remove_all_filters( 'bbp_get_topic_content' );
+
+		// Topic post.
+		$this->assertSame( 'Topic 1', bbp_get_topic_title( $t ) );
+		$this->assertSame( 'Content for Topic 1', bbp_get_topic_content( $t ) );
+		$this->assertSame( 'publish', bbp_get_topic_status( $t ) );
+		$this->assertSame( $f, wp_get_post_parent_id( $t ) );
+		$this->assertEquals( 'http://' . WP_TESTS_DOMAIN . '/?topic=' . $topic->post_name, $topic->guid );
+
+		// Topic meta.
+		$this->assertSame( $f, bbp_get_topic_forum_id( $t ) );
+		$this->assertSame( 1,  bbp_get_topic_reply_count( $t, true ) );
+		$this->assertSame( 0,  bbp_get_topic_reply_count_hidden( $t, true ) );
+		$this->assertSame( 2,  bbp_get_topic_voice_count( $t, true ) );
+		$this->assertSame( $r, bbp_get_topic_last_reply_id( $t ) );
+		$this->assertSame( $r, bbp_get_topic_last_active_id( $t ) );
+		$this->assertSame( '4 days, 4 hours ago', bbp_get_topic_last_active_time( $t ) );
+	}
+
+	/**
+	 * @covers ::bbp_new_topic_handler
+	 * @todo   Implement test_bbp_new_topic_handler().
+	 */
+	public function test_bbp_new_topic_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_topic_handler
+	 * @todo   Implement test_bbp_edit_topic_handler().
+	 */
+	public function test_bbp_edit_topic_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_topic
+	 * @todo   Implement test_bbp_update_topic().
+	 */
+	public function test_bbp_update_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_walker
+	 * @todo   Implement test_bbp_update_topic_walker().
+	 */
+	public function test_bbp_update_topic_walker() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_move_topic_handler
+	 */
+	public function test_bbp_move_topic_handler() {
+		$old_current_user = 0;
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
+		$this->keymaster_id = get_current_user_id();
+		bbp_set_user_role( $this->keymaster_id, bbp_get_keymaster_role() );
+
+		$old_forum_id = $this->factory->forum->create();
+		$topic_id = $this->factory->topic->create( array(
+			'post_parent' => $old_forum_id,
+			'topic_meta' => array(
+				'forum_id' => $old_forum_id,
+			),
+		) );
+
+		$reply_id = $this->factory->reply->create( array(
+			'post_parent' => $topic_id,
+			'reply_meta' => array(
+				'forum_id' => $old_forum_id,
+				'topic_id' => $topic_id,
+			),
+		) );
+
+		// Topic post parent
+		$topic_parent = wp_get_post_parent_id( $topic_id );
+		$this->assertSame( $old_forum_id, $topic_parent );
+
+		// Forum meta
+		$this->assertSame( 1, bbp_get_forum_topic_count( $old_forum_id, true, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $old_forum_id, true, true ) );
+		$this->assertSame( $topic_id, bbp_get_forum_last_topic_id( $old_forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_reply_id( $old_forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_active_id( $old_forum_id ) );
+
+		// Topic meta
+		$this->assertSame( $old_forum_id, bbp_get_topic_forum_id( $topic_id ) );
+		$this->assertSame( 1, bbp_get_topic_voice_count( $topic_id, true ) );
+		$this->assertSame( 1, bbp_get_topic_reply_count( $topic_id, true ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_reply_id( $topic_id ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_active_id( $topic_id ) );
+
+		// Reply Meta
+		$this->assertSame( $old_forum_id, bbp_get_reply_forum_id( $reply_id ) );
+		$this->assertSame( $topic_id, bbp_get_reply_topic_id( $reply_id ) );
+
+		// Create a new forum
+		$new_forum_id = $this->factory->forum->create();
+
+		// Move the topic into the new forum
+		bbp_move_topic_handler( $topic_id, $old_forum_id, $new_forum_id );
+
+		// Topic post parent
+		$topic_parent = wp_get_post_parent_id( $topic_id );
+		$this->assertSame( $new_forum_id, $topic_parent );
+
+		// Forum meta
+		$this->assertSame( 1, bbp_get_forum_topic_count( $new_forum_id, true, true ) );
+		$this->assertSame( 1, bbp_get_forum_reply_count( $new_forum_id, true, true ) );
+		$this->assertSame( $topic_id, bbp_get_forum_last_topic_id( $new_forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_reply_id( $new_forum_id ) );
+		$this->assertSame( $reply_id, bbp_get_forum_last_active_id( $new_forum_id ) );
+
+		// Topic meta
+		$this->assertSame( $new_forum_id, bbp_get_topic_forum_id( $topic_id ) );
+		$this->assertSame( 1, bbp_get_topic_voice_count( $topic_id, true ) );
+		$this->assertSame( 1, bbp_get_topic_reply_count( $topic_id, true ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_reply_id( $topic_id ) );
+		$this->assertSame( $reply_id, bbp_get_topic_last_active_id( $topic_id ) );
+
+		// Reply Meta
+		$this->assertSame( $new_forum_id, bbp_get_reply_forum_id( $reply_id ) );
+		$this->assertSame( $topic_id, bbp_get_reply_topic_id( $reply_id ) );
+
+		// Old Topic/Reply Counts
+		$this->assertSame( 0, bbp_get_forum_topic_count( $old_forum_id, true, true ) );
+		$this->assertSame( 0, bbp_get_forum_reply_count( $old_forum_id, true, true ) );
+
+
+		// Retore the user
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_merge_topic_handler
+	 * @todo   Implement test_bbp_merge_topic_handler().
+	 */
+	public function test_bbp_merge_topic_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_merge_topic_count
+	 * @todo   Implement test_bbp_merge_topic_count().
+	 */
+	public function test_bbp_merge_topic_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_split_topic_handler
+	 * @todo   Implement test_bbp_split_topic_handler().
+	 */
+	public function test_bbp_split_topic_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_split_topic_count
+	 * @todo   Implement test_bbp_split_topic_count().
+	 */
+	public function test_bbp_split_topic_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_statuses
+	 * @todo   Implement test_bbp_get_topic_statuses().
+	 */
+	public function test_bbp_get_topic_statuses() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_types
+	 * @todo   Implement test_bbp_get_topic_types().
+	 */
+	public function test_bbp_get_topic_types() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_stickies
+	 * @todo   Implement test_bbp_get_stickies().
+	 */
+	public function test_bbp_get_stickies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_super_stickies
+	 * @todo   Implement test_bbp_get_super_stickies().
+	 */
+	public function test_bbp_get_super_stickies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_toggle_topic_handler
+	 * @todo   Implement test_bbp_toggle_topic_handler().
+	 */
+	public function test_bbp_toggle_topic_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_remove_topic_from_all_favorites
+	 * @todo   Implement test_bbp_remove_topic_from_all_favorites().
+	 */
+	public function test_bbp_remove_topic_from_all_favorites() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_remove_topic_from_all_subscriptions
+	 * @todo   Implement test_bbp_remove_topic_from_all_subscriptions().
+	 */
+	public function test_bbp_remove_topic_from_all_subscriptions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_forum_id
+	 */
+	public function test_bbp_update_topic_forum_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$forum_id = bbp_get_topic_forum_id( $t );
+		$this->assertSame( $f, $forum_id );
+
+		$topic_parent = wp_get_post_parent_id( $t );
+		$this->assertSame( $f, $topic_parent );
+
+		$this->assertTrue( delete_post_meta_by_key( '_bbp_forum_id' ) );
+
+		bbp_update_topic_forum_id( $t, $f );
+
+		$forum_id = bbp_get_topic_forum_id( $t );
+		$this->assertSame( $f, $forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_topic_id
+	 * @todo   Implement test_bbp_update_topic_topic_id().
+	 */
+	public function test_bbp_update_topic_topic_id() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_revision_log
+	 * @todo   Implement test_bbp_update_topic_revision_log().
+	 */
+	public function test_bbp_update_topic_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_delete_topic
+	 * @todo   Implement test_bbp_delete_topic().
+	 */
+	public function test_bbp_delete_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_delete_topic_replies
+	 */
+	public function test_bbp_delete_topic_replies() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$this->assertSame( 2, bbp_get_topic_reply_count( $t, true ) );
+
+		bbp_delete_topic_replies( $t );
+
+		$count = count( bbp_get_all_child_ids( $t, bbp_get_reply_post_type() ) );
+		$this->assertSame( 0, ( $count ) );
+	}
+
+	/**
+	 * @covers ::bbp_trash_topic
+	 * @todo   Implement test_bbp_trash_topic().
+	 */
+	public function test_bbp_trash_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trash_topic_replies
+	 * @todo   Implement test_bbp_trash_topic_replies().
+	 */
+	public function test_bbp_trash_topic_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrash_topic
+	 * @todo   Implement test_bbp_untrash_topic().
+	 */
+	public function test_bbp_untrash_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrash_topic_replies
+	 * @todo   Implement test_bbp_untrash_topic_replies().
+	 */
+	public function test_bbp_untrash_topic_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_deleted_topic
+	 * @todo   Implement test_bbp_deleted_topic().
+	 */
+	public function test_bbp_deleted_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_trashed_topic
+	 * @todo   Implement test_bbp_trashed_topic().
+	 */
+	public function test_bbp_trashed_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_untrashed_topic
+	 * @todo   Implement test_bbp_untrashed_topic().
+	 */
+	public function test_bbp_untrashed_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topics_per_page
+	 * @todo   Implement test_bbp_get_topics_per_page().
+	 */
+	public function test_bbp_get_topics_per_page() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topics_per_rss_page
+	 * @todo   Implement test_bbp_get_topics_per_rss_page().
+	 */
+	public function test_bbp_get_topics_per_rss_page() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_content_autoembed
+	 * @todo   Implement test_bbp_topic_content_autoembed().
+	 */
+	public function test_bbp_topic_content_autoembed() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_display_topics_feed_rss2
+	 * @todo   Implement test_bbp_display_topics_feed_rss2().
+	 */
+	public function test_bbp_display_topics_feed_rss2() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_topic_edit
+	 * @todo   Implement test_bbp_check_topic_edit().
+	 */
+	public function test_bbp_check_topic_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/functions/update-last-thing.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/functions/update-last-thing.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/functions/update-last-thing.php	(revision 7162)
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Tests for the `bbp_update_topic_last_*()` functions.
+ *
+ * @group topics
+ * @group functions
+ * @group update_last_thing
+ */
+
+class BBP_Tests_Topics_Functions_Update_Topic_Last_Thing extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_update_topic_last_active_id
+	 */
+	public function test_bbp_update_topic_last_active_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Pass both the topic id and reply id to bbp_update_topic_last_active_id().
+		bbp_update_topic_last_active_id( $t, $r1 );
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r1, $last_active_id );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Pass the topic id to bbp_update_topic_last_active_id().
+		bbp_update_topic_last_active_id( $t );
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r2, $last_active_id );
+
+		// Create a couple of replies.
+		$r3 = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Pass both the topic id and reply id of the array.
+		bbp_update_topic_last_active_id( $t, $r3[1] );
+		$last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r3[1], $last_active_id );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_last_active_time
+	 */
+	public function test_bbp_update_topic_last_active_time() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$r1_time_raw       = get_post_field( 'post_date', $r1 );
+		$r1_time_formatted = bbp_get_time_since( bbp_convert_date( $r1_time_raw ) );
+
+		$time = bbp_update_topic_last_active_time( $t, $r1_time_raw );
+		$this->assertSame( $r1_time_raw, $time );
+
+		$time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $r1_time_formatted, $time );
+
+		$r2 = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$r2_time_raw       = get_post_field( 'post_date', $r2[1] );
+		$r2_time_formatted = bbp_get_time_since( bbp_convert_date( $r2_time_raw ) );
+
+		bbp_update_topic_last_active_time( $t );
+		$time = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $r2_time_formatted, $time );
+	}
+
+	/**
+	 * @covers ::bbp_update_topic_last_reply_id
+	 */
+	public function test_bbp_update_topic_last_reply_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$id = bbp_update_topic_last_reply_id( $t, $r1 );
+		$this->assertSame( $r1, $id );
+
+		$id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( $r1, $id );
+
+		$r2 = $this->factory->reply->create_many( 2, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_update_topic_last_reply_id( $t, $r2[1] );
+		$id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( $r2[1], $id );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/authors.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/authors.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/authors.php	(revision 7162)
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_topic_author_*()` template functions.
+ *
+ * @group topics
+ * @group template
+ * @group authors
+ */
+class BBP_Tests_Topics_Template_Authors extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_author
+	 * @covers ::bbp_get_topic_author
+	 * @todo   Implement test_bbp_get_topic_author().
+	 */
+	public function test_bbp_get_topic_author() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_id
+	 * @covers ::bbp_get_topic_author_id
+	 */
+	public function test_bbp_get_topic_author_id() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+		) );
+
+		$topic = bbp_get_topic_author_id( $t );
+		$this->assertSame( $u, $topic );
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_display_name
+	 * @covers ::bbp_get_topic_author_display_name
+	 */
+	public function test_bbp_get_topic_author_display_name() {
+		$u = $this->factory->user->create( array(
+			'display_name' => 'Barry B. Benson',
+		) );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+		) );
+
+		$topic = bbp_get_topic_author_display_name( $t );
+		$this->assertSame( 'Barry B. Benson', $topic );
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_avatar
+	 * @covers ::bbp_get_topic_author_avatar
+	 * @todo   Implement test_bbp_get_topic_author_avatar().
+	 */
+	public function test_bbp_get_topic_author_avatar() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_link
+	 * @covers ::bbp_get_topic_author_link
+	 * @todo   Implement test_bbp_get_topic_author_link().
+	 */
+	public function test_bbp_get_topic_author_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_url
+	 * @covers ::bbp_get_topic_author_url
+	 * @todo   Implement test_bbp_get_topic_author_url().
+	 */
+	public function test_bbp_get_topic_author_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_email
+	 * @covers ::bbp_get_topic_author_email
+	 * @todo   Implement test_bbp_get_topic_author_email().
+	 */
+	public function test_bbp_get_topic_author_email() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_author_role
+	 * @covers ::bbp_get_topic_author_role
+	 * @todo   Implement test_bbp_get_topic_author_role().
+	 */
+	public function test_bbp_get_topic_author_role() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/counts.php	(revision 7162)
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * Tests for the topic component count template functions.
+ *
+ * @group topics
+ * @group template
+ * @group counts
+ */
+class BBP_Tests_Topics_Template_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_reply_count
+	 * @covers ::bbp_get_topic_reply_count
+	 */
+	public function test_bbp_get_topic_reply_count() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->reply->create_many( $int_value, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_update_topic_reply_count( $t );
+
+		// Output
+		$this->expectOutputString( $formatted_value );
+		bbp_topic_reply_count( $t );
+
+		// Formatted string
+		$count = bbp_get_topic_reply_count( $t, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Integer
+		$count = bbp_get_topic_reply_count( $t, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_topic_post_count
+	 * @covers ::bbp_get_topic_post_count
+	 */
+	public function test_bbp_get_topic_post_count() {
+		$f = $this->factory->forum->create();
+
+		$int_topics  = 1;
+		$int_replies = 3;
+		$int_value   = $int_topics + $int_replies;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$this->factory->reply->create_many( $int_replies, array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_update_topic_reply_count( $t );
+
+		// Output
+		$this->expectOutputString( $formatted_value );
+		bbp_topic_post_count( $t );
+
+		// Formatted string
+		$count = bbp_get_topic_post_count( $t, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Integer
+		$count = bbp_get_topic_post_count( $t, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_topic_reply_count_hidden
+	 * @covers ::bbp_get_topic_reply_count_hidden
+	 */
+	public function test_bbp_get_topic_reply_count_hidden() {
+		$f = $this->factory->forum->create();
+
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$r = $this->factory->reply->create_many( $int_value, array(
+			'post_parent' => $t,
+			'post_status' => bbp_get_spam_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_update_topic_reply_count_hidden( $t );
+
+		bbp_spam_reply( $r[1] );
+
+		// Output
+		$this->expectOutputString( $formatted_value );
+		bbp_topic_reply_count_hidden( $t );
+
+		// Formatted string
+		$count = bbp_get_topic_reply_count_hidden( $t, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Integer
+		$count = bbp_get_topic_reply_count_hidden( $t, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_topic_voice_count
+	 * @covers ::bbp_get_topic_voice_count
+	 */
+	public function test_bbp_get_topic_voice_count() {
+		$u = $this->factory->user->create_many( 2 );
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_author' => $u[0],
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$int_value = 2;
+		$formatted_value = bbp_number_format( $int_value );
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'post_author' => $u[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'post_author' => $u[1],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		bbp_update_topic_voice_count( $t );
+
+		// Output
+		$this->expectOutputString( $formatted_value );
+		bbp_topic_voice_count( $t );
+
+		// Formatted string
+		$count = bbp_get_topic_voice_count( $t, false );
+		$this->assertSame( $formatted_value, $count );
+
+		// Integer
+		$count = bbp_get_topic_voice_count( $t, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_topic_voice_count
+	 * @covers ::bbp_get_topic_voice_count
+	 */
+	public function test_bbp_get_topic_voice_count_with_pending_reply() {
+		$u = $this->factory->user->create_many( 2 );
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_parent' => $u[0],
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u[0],
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 1, $count );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u[1],
+			'post_status' => bbp_get_pending_status_id(),
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 1, $count );
+
+		bbp_approve_reply( $r2 );
+
+		$count = bbp_update_topic_voice_count( $t );
+		$this->assertSame( 2, $count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/forms.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/forms.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/forms.php	(revision 7162)
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_topic_*_()` functions.
+ *
+ * @group topics
+ * @group template
+ * @group forms
+ */
+class BBP_Tests_Topics_Template_Forms extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_form_topic_type_dropdown
+	 * @covers ::bbp_get_form_topic_type_dropdown
+	 * @todo   Implement test_bbp_get_form_topic_type_dropdown().
+	 */
+	public function test_bbp_get_form_topic_type_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_status_dropdown
+	 * @covers ::bbp_get_form_topic_status_dropdown
+	 * @todo   Implement test_bbp_get_form_topic_status_dropdown().
+	 */
+	public function test_bbp_get_form_topic_status_dropdown() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_title
+	 * @covers ::bbp_get_form_topic_title
+	 * @todo   Implement test_bbp_get_form_topic_title().
+	 */
+	public function test_bbp_get_form_topic_title() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_content
+	 * @covers ::bbp_get_form_topic_content
+	 * @todo   Implement test_bbp_get_form_topic_content().
+	 */
+	public function test_bbp_get_form_topic_content() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_tags
+	 * @covers ::bbp_get_form_topic_tags
+	 * @todo   Implement test_bbp_get_form_topic_tags().
+	 */
+	public function test_bbp_get_form_topic_tags() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_forum
+	 * @covers ::bbp_get_form_topic_forum
+	 * @todo   Implement test_bbp_get_form_topic_forum().
+	 */
+	public function test_bbp_get_form_topic_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_subscribed
+	 * @covers ::bbp_get_form_topic_subscribed
+	 * @todo   Implement test_bbp_get_form_topic_subscribed().
+	 */
+	public function test_bbp_get_form_topic_subscribed() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_log_edit
+	 * @covers ::bbp_get_form_topic_log_edit
+	 * @todo   Implement test_bbp_get_form_topic_log_edit().
+	 */
+	public function test_bbp_get_form_topic_log_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_form_topic_edit_reason
+	 * @covers ::bbp_get_form_topic_edit_reason
+	 * @todo   Implement test_bbp_get_form_topic_edit_reason().
+	 */
+	public function test_bbp_get_form_topic_edit_reason() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_form_post_request
+	 * @todo   Implement test_bbp_is_topic_form_post_request().
+	 */
+	public function test_bbp_is_topic_form_post_request() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/get-last-thing.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/get-last-thing.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/get-last-thing.php	(revision 7162)
@@ -0,0 +1,206 @@
+<?php
+
+/**
+ * Tests for the `bbp_get_topic__last_*()` template functions.
+ *
+ * @group topics
+ * @group template
+ * @group get_last_thing
+ */
+class BBP_Tests_Topics_Template_Get_Topic_Last_Thing extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_last_active_id
+	 * @covers ::bbp_get_topic_last_active_id
+	 */
+	public function test_bbp_get_topic_last_active_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$topic_last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $t, $topic_last_active_id );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$topic_last_active_id = bbp_get_topic_last_active_id( $t );
+		$this->assertSame( $r, $topic_last_active_id );
+	}
+
+	/**
+	 * @covers ::bbp_topic_last_active_time
+	 * @covers ::bbp_get_topic_last_active_time
+	 */
+	public function test_bbp_get_topic_last_active_time() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date_topic = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_reply = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+
+		$topic_time = '4 days, 4 hours ago';
+		$reply_time = '3 days, 8 hours ago';
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date_topic,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Output.
+		$this->expectOutputString( $topic_time );
+		bbp_topic_last_active_time( $t );
+
+		// Topic time.
+		$datetime = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( $topic_time, $datetime );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_reply,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		// Reply time.
+		$datetime = bbp_get_topic_last_active_time( $t );
+		$this->assertSame( '3 days, 8 hours ago', $datetime );
+
+	}
+
+	/**
+	 * @covers ::bbp_topic_last_reply_id
+	 * @covers ::bbp_get_topic_last_reply_id
+	 */
+	public function test_bbp_get_topic_last_reply_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$topic_last_reply_id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( 0, $topic_last_reply_id );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$topic_last_reply_id = bbp_get_topic_last_reply_id( $t );
+		$this->assertSame( $r, $topic_last_reply_id );
+	}
+
+	/**
+	 * @covers ::bbp_topic_last_reply_title
+	 * @covers ::bbp_get_topic_last_reply_title
+	 */
+	public function test_bbp_get_topic_last_reply_title() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$title = bbp_get_topic_last_reply_title( $t );
+		$this->assertSame( '', $title );
+
+		$this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$title = bbp_get_topic_last_reply_title( $t );
+		$this->assertSame( 'Reply To: ' . bbp_get_topic_title( $t ), $title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_last_reply_permalink
+	 * @covers ::bbp_get_topic_last_reply_permalink
+	 */
+	public function test_bbp_get_topic_last_reply_permalink() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$topic_last_reply_permalink = bbp_get_forum_last_topic_permalink( $f );
+		$this->assertSame( bbp_get_topic_permalink( $t ), $topic_last_reply_permalink );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$topic_last_reply_permalink = bbp_get_topic_last_reply_permalink( $t );
+		$this->assertSame( bbp_get_reply_permalink( $r ), $topic_last_reply_permalink );
+	}
+
+	/**
+	 * @covers ::bbp_topic_last_reply_url
+	 * @covers ::bbp_get_topic_last_reply_url
+	 */
+	public function test_bbp_get_topic_last_reply_url() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$f = $this->factory->forum->create();
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			)
+		) );
+
+		$topic_last_reply_url = bbp_get_topic_last_reply_url( $t );
+		$this->assertSame( get_permalink( $t ), $topic_last_reply_url );
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			)
+		) );
+
+		$topic_last_reply_url = bbp_get_topic_last_reply_url( $t );
+		$this->assertSame( bbp_get_reply_url( $r ), $topic_last_reply_url );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/links.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/links.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/links.php	(revision 7162)
@@ -0,0 +1,355 @@
+<?php
+
+/**
+ * Tests for the topics component link template functions.
+ *
+ * @group topics
+ * @group template
+ * @group links
+ */
+class BBP_Tests_Topics_Template_Links extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_subscription_link
+	 * @covers ::bbp_get_topic_subscription_link
+	 * @todo   Implement test_bbp_get_topic_subscription_link().
+	 */
+	public function test_bbp_get_topic_subscription_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_favorite_link
+	 * @covers ::bbp_get_topic_favorite_link
+	 * @todo   Implement test_bbp_get_topic_favorite_link().
+	 */
+	public function test_bbp_get_topic_favorite_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_freshness_link
+	 * @covers ::bbp_get_topic_freshness_link
+	 */
+	public function test_bbp_get_topic_freshness_link() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$now = time();
+		$post_date    = date( 'Y-m-d H:i:s', $now - 60 * 60 * 100 );
+		$post_date_r1 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 80 );
+		$post_date_r2 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 60 );
+
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="">4 days, 4 hours ago</a>', $link );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r1,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r1 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">3 days, 8 hours ago</a>', $link );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r2 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">2 days, 12 hours ago</a>', $link );
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_freshness_link
+	 */
+	public function test_bbp_get_topic_freshness_link_with_unpublished_replies() {
+
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+
+		$now = time();
+		$post_date    = date( 'Y-m-d H:i:s', $now - 60 * 60 * 20 ); // 2o hours ago
+		$post_date_r1 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 18 ); // 18 hours ago
+		$post_date_r2 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 16 ); // 16 hours ago
+		$post_date_r3 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 14 ); // 14 hours ago
+		$post_date_r4 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 12 ); // 12 hours ago
+		$post_date_r5 = date( 'Y-m-d H:i:s', $now - 60 * 60 * 10 ); // 1o hours ago
+
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1" title="">20 hours ago</a>', $link );
+
+		$r1 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r1,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r1 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">18 hours ago</a>', $link );
+
+		$r2 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r2,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r2 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">16 hours ago</a>', $link );
+
+		bbp_spam_reply( $r2 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r1 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">18 hours ago</a>', $link );
+
+		$r3 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r3,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r3 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">14 hours ago</a>', $link );
+
+		// Todo: Use bbp_trash_reply() and not wp_trash_post()
+		wp_trash_post( $r3 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r1 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">18 hours ago</a>', $link );
+
+		$r4 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r4,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r4 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">12 hours ago</a>', $link );
+
+		bbp_unapprove_reply( $r4 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r1 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">18 hours ago</a>', $link );
+
+		bbp_unspam_reply( $r2 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r2 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">16 hours ago</a>', $link );
+
+		// Todo: Use bbp_untrash_reply() and not wp_untrash_post()
+		wp_untrash_post( $r3 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r3 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">14 hours ago</a>', $link );
+
+		bbp_approve_reply( $r4 );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r4 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">12 hours ago</a>', $link );
+
+		$r5 = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_date' => $post_date_r5,
+			'reply_meta' => array(
+				'forum_id' => $f,
+				'topic_id' => $t,
+			),
+		) );
+
+		$link = bbp_get_topic_freshness_link( $t );
+		$this->assertSame( '<a href="http://' . WP_TESTS_DOMAIN . '/?topic=topic-1#post-' . bbp_get_reply_id( $r5 ) . '" title="Reply To: ' . bbp_get_topic_title( $t ) . '">10 hours ago</a>', $link );
+	}
+
+	/**
+	 * @covers ::bbp_topic_replies_link
+	 * @covers ::bbp_get_topic_replies_link
+	 * @todo   Implement test_bbp_get_topic_replies_link().
+	 */
+	public function test_bbp_get_topic_replies_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_admin_links
+	 * @covers ::bbp_get_topic_admin_links
+	 * @todo   Implement test_bbp_get_topic_admin_links().
+	 */
+	public function test_bbp_get_topic_admin_links() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_edit_link
+	 * @covers ::bbp_get_topic_edit_link
+	 * @todo   Implement test_bbp_get_topic_edit_link().
+	 */
+	public function test_bbp_get_topic_edit_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_edit_url
+	 * @covers ::bbp_get_topic_edit_url
+	 * @todo   Implement test_bbp_get_topic_edit_url().
+	 */
+	public function test_bbp_get_topic_edit_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_trash_link
+	 * @covers ::bbp_get_topic_trash_link
+	 * @todo   Implement test_bbp_get_topic_trash_link().
+	 */
+	public function test_bbp_get_topic_trash_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_close_link
+	 * @covers ::bbp_get_topic_close_link
+	 * @todo   Implement test_bbp_get_topic_close_link().
+	 */
+	public function test_bbp_get_topic_close_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_approve_link
+	 * @covers ::bbp_get_topic_approve_link
+	 * @todo   Implement test_bbp_get_topic_approve_link().
+	 */
+	public function test_bbp_get_topic_approve_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_stick_link
+	 * @covers ::bbp_get_topic_stick_link
+	 * @todo   Implement test_bbp_get_topic_stick_link().
+	 */
+	public function test_bbp_get_topic_stick_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_merge_link
+	 * @covers ::bbp_get_topic_merge_link
+	 * @todo   Implement test_bbp_get_topic_merge_link().
+	 */
+	public function test_bbp_get_topic_merge_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_spam_link
+	 * @covers ::bbp_get_topic_spam_link
+	 * @todo   Implement test_bbp_get_topic_spam_link().
+	 */
+	public function test_bbp_get_topic_spam_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_reply_link
+	 * @covers ::bbp_get_topic_reply_link
+	 * @todo   Implement test_bbp_get_topic_reply_link().
+	 */
+	public function test_bbp_get_topic_reply_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_pagination_links
+	 * @covers ::bbp_get_forum_pagination_links
+	 * @todo   Implement test_bbp_get_forum_pagination_links().
+	 */
+	public function test_bbp_get_forum_pagination_links() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/loop.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/loop.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/loop.php	(revision 7162)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Tests for the topic loop functions.
+ *
+ * @group topics
+ * @group template
+ * @group loop
+ */
+class BBP_Tests_Topics_Template_Topic_Loop extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_has_topics
+	 * @todo   Implement test_bbp_has_topics().
+	 */
+	public function test_bbp_has_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topics
+	 * @todo   Implement test_bbp_topics().
+	 */
+	public function test_bbp_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_the_topic
+	 * @todo   Implement test_bbp_the_topic().
+	 */
+	public function test_bbp_the_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/post_type.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/post_type.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/post_type.php	(revision 7162)
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Tests for the `bbp_*_form_topic_post_type_*()` functions.
+ *
+ * @group topics
+ * @group template
+ * @group post_type
+ */
+class BBP_Tests_Topics_Template_Post_Type extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_post_type
+	 * @covers ::bbp_get_topic_post_type
+	 */
+	public function test_bbp_topic_post_type() {
+		$t = $this->factory->topic->create();
+
+		$tobj = get_post_type_object( 'topic' );
+
+		// WordPress 4.6 introduced `WP_Post_Type` class
+		if ( bbp_get_major_wp_version() < 4.6 ) {
+			$this->assertInstanceOf( 'stdClass', $tobj );
+		} else {
+			$this->assertInstanceOf( 'WP_Post_Type', $tobj );
+		}
+
+		$this->assertEquals( 'topic', $tobj->name );
+
+		// Test some defaults
+		$this->assertFalse( is_post_type_hierarchical( 'topic' ) );
+
+		$topic_type = bbp_topic_post_type( $t );
+		$this->expectOutputString( 'topic', $topic_type );
+
+		$topic_type = bbp_get_topic_post_type( $t );
+		$this->assertSame( 'topic', $topic_type );
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_post_type_labels
+	 * @todo   Implement test_bbp_get_topic_post_type_labels().
+	 */
+	public function test_bbp_get_topic_post_type_labels() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_post_type_rewrite
+	 * @todo   Implement test_bbp_get_topic_post_type_rewrite().
+	 */
+	public function test_bbp_get_topic_post_type_rewrite() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_post_type_supports
+	 * @todo   Implement test_bbp_get_topic_post_type_supports().
+	 */
+	public function test_bbp_get_topic_post_type_supports() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/revisions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/revisions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/revisions.php	(revision 7162)
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Tests for the topic revision template functions.
+ *
+ * @group topics
+ * @group template
+ * @group revisions
+ */
+class BBP_Tests_Topics_Template_Revisions extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_content_append_revisions
+	 * @todo   Implement test_bbp_topic_content_append_revisions().
+	 */
+	public function test_bbp_topic_content_append_revisions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_revision_log
+	 * @covers ::bbp_get_topic_revision_log
+	 * @todo   Implement test_bbp_get_topic_revision_log().
+	 */
+	public function test_bbp_get_topic_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_raw_revision_log
+	 * @todo   Implement test_bbp_get_topic_raw_revision_log().
+	 */
+	public function test_bbp_get_topic_raw_revision_log() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_revisions
+	 * @todo   Implement test_bbp_get_topic_revisions().
+	 */
+	public function test_bbp_get_topic_revisions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_revision_count
+	 * @todo   Implement test_bbp_get_topic_revision_count().
+	 */
+	public function test_bbp_get_topic_revision_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/status.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/status.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/status.php	(revision 7162)
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * Tests for the topic status template functions.
+ *
+ * @group topics
+ * @group template
+ * @group status
+ */
+class BBP_Tests_Topics_Template_Status extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_is_topic_sticky
+	 * @todo   Implement test_bbp_is_topic_sticky().
+	 */
+	public function test_bbp_is_topic_sticky() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_super_sticky
+	 * @todo   Implement test_bbp_is_topic_super_sticky().
+	 */
+	public function test_bbp_is_topic_super_sticky() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_status
+	 * @todo   Implement test_bbp_topic_status().
+	 */
+	public function test_bbp_topic_status() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_status
+	 * @todo   Implement test_bbp_get_topic_status().
+	 */
+	public function test_bbp_get_topic_status() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_closed
+	 * @todo   Implement test_bbp_is_topic_closed().
+	 */
+	public function test_bbp_is_topic_closed() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_open
+	 * @todo   Implement test_bbp_is_topic_open().
+	 */
+	public function test_bbp_is_topic_open() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_published
+	 * @todo   Implement test_bbp_is_topic_published().
+	 */
+	public function test_bbp_is_topic_published() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_spam
+	 * @todo   Implement test_bbp_is_topic_spam().
+	 */
+	public function test_bbp_is_topic_spam() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_trash
+	 * @todo   Implement test_bbp_is_topic_trash().
+	 */
+	public function test_bbp_is_topic_trash() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_pending
+	 * @todo   Implement test_bbp_is_topic_pending().
+	 */
+	public function test_bbp_is_topic_pending() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_private
+	 * @todo   Implement test_bbp_is_topic_private().
+	 */
+	public function test_bbp_is_topic_private() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_topic_anonymous
+	 * @todo   Implement test_bbp_is_topic_anonymous().
+	 */
+	public function test_bbp_is_topic_anonymous() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/topic-tag.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/topic-tag.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/topic-tag.php	(revision 7162)
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * Tests for the topic tag template functions.
+ *
+ * @group topics
+ * @group topic_tags
+ * @group template
+ * @group topic
+ * @group topic_tag
+ */
+class BBP_Tests_Topic_Tags_Template_Topic_Tag extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_topic_tag_list
+	 * @covers ::bbp_get_topic_tag_list
+	 * @todo   Implement test_bbp_get_topic_tag_list().
+	 */
+	public function test_bbp_get_topic_tag_list() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_tax_id
+	 * @covers ::bbp_get_topic_tag_tax_id
+	 * @todo   Implement test_bbp_get_topic_tag_tax_id().
+	 */
+	public function test_bbp_get_topic_tag_tax_id() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_tag_tax_labels
+	 * @todo   Implement test_bbp_get_topic_tag_tax_labels().
+	 */
+	public function test_bbp_get_topic_tag_tax_labels() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_tag_tax_rewrite
+	 * @todo   Implement test_bbp_get_topic_tag_tax_rewrite().
+	 */
+	public function test_bbp_get_topic_tag_tax_rewrite() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_id
+	 * @covers ::bbp_get_topic_tag_id
+	 * @todo   Implement test_bbp_get_topic_tag_id().
+	 */
+	public function test_bbp_get_topic_tag_id() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_name
+	 * @covers ::bbp_get_topic_tag_name
+	 * @todo   Implement test_bbp_get_topic_tag_name().
+	 */
+	public function test_bbp_get_topic_tag_name() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_slug
+	 * @covers ::bbp_get_topic_tag_slug
+	 * @todo   Implement test_bbp_get_topic_tag_slug().
+	 */
+	public function test_bbp_get_topic_tag_slug() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_link
+	 * @covers ::bbp_get_topic_tag_link
+	 * @todo   Implement test_bbp_get_topic_tag_link().
+	 */
+	public function test_bbp_get_topic_tag_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_edit_link
+	 * @covers ::bbp_get_topic_tag_edit_link
+	 * @todo   Implement test_bbp_get_topic_tag_edit_link().
+	 */
+	public function test_bbp_get_topic_tag_edit_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_tag_description
+	 * @covers ::bbp_get_topic_tag_description
+	 * @todo   Implement test_bbp_get_topic_tag_description().
+	 */
+	public function test_bbp_get_topic_tag_description() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/topics/template/topic.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/topics/template/topic.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/topics/template/topic.php	(revision 7162)
@@ -0,0 +1,393 @@
+<?php
+/**
+ * Tests for the topics component topic template functions.
+ *
+ * @group topics
+ * @group template
+ * @group topic
+ */
+class BBP_Tests_Topics_Template_Topic extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_show_lead_topic
+	 * @todo   Implement test_bbp_show_lead_topic().
+	 */
+	public function test_bbp_show_lead_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_id
+	 * @covers ::bbp_get_topic_id
+	 */
+	public function test_bbp_get_topic_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_id = bbp_get_topic_id( $t );
+		$this->assertSame( $t, $topic_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_topic
+	 * @todo   Implement test_bbp_get_topic().
+	 */
+	public function test_bbp_get_topic() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_permalink
+	 * @covers ::bbp_get_topic_permalink
+	 */
+	public function test_bbp_get_topic_permalink() {
+		if ( is_multisite() ) {
+			$this->markTestSkipped( 'Skipping URL tests in multiste for now.' );
+		}
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_permalink = bbp_get_topic_permalink( $t );
+
+		$this->expectOutputString( $topic_permalink );
+		bbp_topic_permalink( $t );
+
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/?topic=topic-1', $topic_permalink );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 */
+	public function test_bbp_get_topic_title() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title' => 'Topic 1',
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( 'Topic 1', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 * @group  bbp_xss
+	 */
+	public function test_bbp_get_topic_title_with_script_and_quotes() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title'  => '<script src="https://bbpress.org">Script</script> Topic',
+			'post_parent' => $f,
+			'topic_meta'  => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( 'Script Topic', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 * @group  bbp_xss
+	 */
+	public function test_bbp_get_topic_title_with_script_no_quotes() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title'  => '<script src=https://bbpress.org>Script</script> Topic',
+			'post_parent' => $f,
+			'topic_meta'  => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( 'Script Topic', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 * @group  bbp_xss
+	 */
+	public function test_bbp_get_topic_title_with_quotes() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title'  => '"Quoted" Topic',
+			'post_parent' => $f,
+			'topic_meta'  => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( '&#8220;Quoted&#8221; Topic', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 * @group  bbp_xss
+	 */
+	public function test_bbp_get_topic_title_with_js_as_img_src() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title'  => '<img src="javascript:alert(\'Oh, bother!\');">Topic 1',
+			'post_parent' => $f,
+			'topic_meta'  => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( 'Topic 1', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_title
+	 * @covers ::bbp_get_topic_title
+	 * @group  bbp_xss
+	 */
+	public function test_bbp_get_topic_title_with_extra_open_brackets() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_title'  => '<<script>alert("XSS");//<</script>',
+			'post_parent' => $f,
+			'topic_meta'  => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_title = bbp_get_topic_title( $t );
+		$this->assertSame( '&lt;alert(&#8220;XSS&#8221;);//&lt;', $topic_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_archive_title
+	 * @covers ::bbp_get_topic_archive_title
+	 * @todo   Implement test_bbp_get_topic_archive_title().
+	 */
+	public function test_bbp_get_topic_archive_title() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_content
+	 * @covers ::bbp_get_topic_content
+	 */
+	public function test_bbp_get_topic_content() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_content' => 'Content of Topic 1',
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		remove_all_filters( 'bbp_get_topic_content' );
+		$topic_content = bbp_get_topic_content( $t );
+		$this->assertSame( 'Content of Topic 1', $topic_content );
+	}
+
+	/**
+	 * @covers ::bbp_topic_excerpt
+	 * @covers ::bbp_get_topic_excerpt
+	 */
+	public function test_bbp_get_topic_excerpt() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_content'  => 'Talk about telekinetic activity, look at this mess!',
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		remove_all_filters( 'bbp_get_topic_content' );
+		$topic_excerpt = bbp_get_topic_excerpt( $t, 23 );
+		$this->assertSame( 'Talk about telekinetic&hellip;', $topic_excerpt );
+	}
+
+	/**
+	 * @covers ::bbp_topic_post_date
+	 * @covers ::bbp_get_topic_post_date
+	 */
+	public function test_bbp_get_topic_post_date() {
+		$f = $this->factory->forum->create();
+
+		$now = time();
+		$post_date = date( 'Y-m-d H:i:s', $now - 60*60*100 );
+
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'post_date' => $post_date,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Configue our written date time, August 4, 2012 at 2:37 pm.
+		$gmt = false;
+		$date   = get_post_time( get_option( 'date_format' ), $gmt, $t, true );
+		$time   = get_post_time( get_option( 'time_format' ), $gmt, $t, true );
+		$result = sprintf( '%1$s at %2$s', $date, $time );
+
+		// Output, string, August 4, 2012 at 2:37 pm.
+		$this->expectOutputString( $result );
+		bbp_topic_post_date( $t );
+
+		// String, August 4, 2012 at 2:37 pm.
+		$datetime = bbp_get_topic_post_date( $t, false, false );
+		$this->assertSame( $result, $datetime );
+
+		// Humanized string, 4 days, 4 hours ago.
+		$datetime = bbp_get_topic_post_date( $t, true, false );
+		$this->assertSame( '4 days, 4 hours ago', $datetime );
+
+		// Humanized string using GMT formatted date, 4 days, 4 hours ago.
+		$datetime = bbp_get_topic_post_date( $t, true, true );
+		$this->assertSame( '4 days, 4 hours ago', $datetime );
+	}
+
+	/**
+	 * @covers ::bbp_topic_pagination
+	 * @covers ::bbp_get_topic_pagination
+	 * @todo   Implement test_bbp_get_topic_pagination().
+	 */
+	public function test_bbp_get_topic_pagination() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_forum_title
+	 * @covers ::bbp_get_topic_forum_title
+	 */
+	public function test_bbp_get_topic_forum_title() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_forum_title = bbp_get_topic_forum_title( $t );
+		$this->assertSame( bbp_get_forum_title( $f ), $topic_forum_title );
+	}
+
+	/**
+	 * @covers ::bbp_topic_forum_id
+	 * @covers ::bbp_get_topic_forum_id
+	 */
+	public function test_bbp_get_topic_forum_id() {
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		$topic_forum_id = bbp_get_topic_forum_id( $t );
+		$this->assertSame( $f, $topic_forum_id );
+	}
+
+	/**
+	 * @covers ::bbp_topic_class
+	 * @covers ::bbp_get_topic_class
+	 * @todo   Implement test_bbp_get_topic_class().
+	 */
+	public function test_bbp_get_topic_class() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_pagination_count
+	 * @covers ::bbp_get_forum_pagination_count
+	 * @todo   Implement test_bbp_get_forum_pagination_count().
+	 */
+	public function test_bbp_get_forum_pagination_count() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_notices
+	 * @todo   Implement test_bbp_topic_notices().
+	 */
+	public function test_bbp_topic_notices() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_type_select
+	 * @todo   Implement test_bbp_topic_type_select().
+	 */
+	public function test_bbp_topic_type_select() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_single_topic_description
+	 * @covers ::bbp_get_single_topic_description
+	 * @todo   Implement test_bbp_get_single_topic_description().
+	 */
+	public function test_bbp_get_single_topic_description() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_topic_row_actions
+	 * @todo   Implement test_bbp_topic_row_actions().
+	 */
+	public function test_bbp_topic_row_actions() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/functions/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/functions/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/functions/counts.php	(revision 7162)
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * Tests for the user component count functions.
+ *
+ * @group users
+ * @group functions
+ * @group counts
+ */
+class BBP_Tests_Users_Functions_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_update_user_reply_count
+	 */
+	function test_bbp_update_user_topic_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+
+		bbp_update_user_topic_count( $u, $int_value );
+
+		$count = bbp_get_user_topic_count( $u, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_update_user_reply_count
+	 */
+	function test_bbp_update_user_reply_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+
+		bbp_update_user_reply_count( $u, $int_value );
+
+		$count = bbp_get_user_reply_count( $u, true );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_user_topic_count
+	 * @covers ::bbp_get_user_topic_count
+	 */
+	function test_bbp_get_user_topic_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		bbp_update_user_topic_count( $u, $int_value );
+
+		$this->expectOutputString( $formatted_value );
+		bbp_user_topic_count( $u );
+
+		$count = bbp_get_user_topic_count( $u, false );
+		$this->assertSame( $formatted_value, $count );
+
+		$count = bbp_get_user_topic_count( $u, true );
+		$this->assertSame( (int) $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_user_reply_count
+	 * @covers ::bbp_get_user_reply_count
+	 */
+	function test_bbp_get_user_reply_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$formatted_value = bbp_number_format( $int_value );
+
+		bbp_update_user_reply_count( $u, $int_value );
+
+		$this->expectOutputString( $formatted_value );
+		bbp_user_reply_count( $u );
+
+		$count = bbp_get_user_reply_count( $u, false );
+		$this->assertSame( $formatted_value, $count );
+
+		$count = bbp_get_user_reply_count( $u, true );
+		$this->assertSame( (int) $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_user_post_count
+	 * @covers ::bbp_get_user_post_count
+	 */
+	function test_bbp_get_user_post_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		// Add reply count
+		bbp_update_user_reply_count( $u, $int_value );
+
+		// Count
+		$count = bbp_get_user_post_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		// Add topic count
+		bbp_update_user_topic_count( $u, $int_value );
+		$double_value = $int_value * 2;
+
+		// Count + Count
+		$double_count = bbp_get_user_post_count( $u, true );
+		$this->assertSame( $double_value, $double_count );
+
+		// Output
+		$double_formatted_value = bbp_number_format( $double_value );
+		$this->expectOutputString( $double_formatted_value );
+		bbp_user_post_count( $u );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_topics_started
+	 */
+	public function test_bbp_get_user_topics_started() {
+		$u = $this->factory->user->create();
+
+		$has_topics = bbp_get_user_topics_started( $u );
+		$this->assertFalse( $has_topics );
+
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_author' => $u,
+		) );
+
+		bbp_update_topic( array(
+			'topic_id' => $t,
+		) );
+
+		$has_topics = bbp_get_user_topics_started( $u );
+		$this->assertTrue( $has_topics );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_replies_created
+	 */
+	public function test_bbp_get_user_replies_created() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create();
+
+		$has_replies = bbp_get_user_replies_created( $u );
+		$this->assertFalse( $has_replies );
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$has_replies = bbp_get_user_replies_created( $u );
+		$this->assertTrue( $has_replies );
+	}
+
+	/**
+	 * @covers ::bbp_get_total_users
+	 */
+	public function test_bbp_get_total_users() {
+		$this->factory->user->create_many( 3 );
+
+		$users = (int) bbp_get_total_users();
+
+		// 15 + 1, the + 1 is the default admin user
+		$this->assertSame( 4, $users );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_topic_count_raw
+	 */
+	public function test_bbp_get_user_topic_count_raw() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_author' => $u,
+		) );
+
+		$count = bbp_get_user_topic_count_raw( $u );
+		$this->assertSame( 3, $count );
+
+		$t = $this->factory->topic->create_many( 3, array(
+			'post_author' => $u,
+		) );
+
+		$count = bbp_get_user_topic_count_raw( $u );
+		$this->assertSame( 6, $count );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+		) );
+
+		bbp_close_topic( $t );
+
+		$count = bbp_get_user_topic_count_raw( $u );
+		$this->assertSame( 7, $count );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_reply_count_raw
+	 */
+	public function test_bbp_get_user_reply_count_raw() {
+		$u = $this->factory->user->create();
+
+		$t = $this->factory->topic->create();
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_get_user_reply_count_raw( $u );
+		$this->assertSame( 3, $count );
+
+		$r = $this->factory->reply->create_many( 3, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		$count = bbp_get_user_reply_count_raw( $u );
+		$this->assertSame( 6, $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_user_topic_count
+	 */
+	public function test_bbp_bump_user_topic_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_topic_count( $u, $int_value );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		bbp_bump_user_topic_count( $u );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value + 1, $count );
+	}
+
+	/**
+	 * @covers ::bbp_bump_user_reply_count
+	 */
+	public function test_bbp_bump_user_reply_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_reply_count( $u, $int_value );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		bbp_bump_user_reply_count( $u );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value + 1, $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_user_topic_count
+	 */
+	public function test_bbp_increase_user_topic_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_topic_count( $u, $int_value );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+		) );
+
+		bbp_increase_user_topic_count( $t );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value + 1, $count );
+	}
+
+	/**
+	 * @covers ::bbp_increase_user_reply_count
+	 */
+	public function test_bbp_increase_user_reply_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_reply_count( $u, $int_value );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		$t = $this->factory->topic->create();
+
+		$r = $this->factory->reply->create_many( $int_value, array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		bbp_increase_user_reply_count( $r );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_user_topic_count
+	 */
+	public function test_bbp_decrease_user_topic_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_topic_count( $u, $int_value );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		$t = $this->factory->topic->create( array(
+			'post_author' => $u,
+		) );
+
+		// Minus 1
+		bbp_decrease_user_topic_count( $t );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value - 1, $count );
+
+		// Minus 2
+		bbp_decrease_user_topic_count( $t );
+
+		$count = bbp_get_user_topic_count( $u, $integer );
+		$this->assertSame( $int_value - 2, $count );
+	}
+
+	/**
+	 * @covers ::bbp_decrease_user_reply_count
+	 */
+	public function test_bbp_decrease_user_reply_count() {
+		$u = $this->factory->user->create();
+		$int_value = 3;
+		$integer = true;
+
+		bbp_update_user_reply_count( $u, $int_value );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value, $count );
+
+		$t = $this->factory->topic->create();
+
+		$r = $this->factory->reply->create( array(
+			'post_parent' => $t,
+			'post_author' => $u,
+			'reply_meta' => array(
+				'topic_id' => $t,
+			),
+		) );
+
+		// Minus 1
+		bbp_decrease_user_reply_count( $r );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value - 1, $count );
+
+		// Minus 2
+		bbp_decrease_user_reply_count( $r );
+
+		$count = bbp_get_user_reply_count( $u, $integer );
+		$this->assertSame( $int_value - 2, $count );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/functions/favorites.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/functions/favorites.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/functions/favorites.php	(revision 7162)
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Tests for the user component favorite functions.
+ *
+ * @group users
+ * @group functions
+ * @group favorites
+ */
+class BBP_Tests_Users_Functions_Favorites extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_topic_favoriters
+	 */
+	public function test_bbp_get_topic_favoriters() {
+		$u = $this->factory->user->create_many( 3 );
+		$t = $this->factory->topic->create();
+
+		// Add topic favorites.
+		bbp_add_user_favorite( $u[0], $t );
+		bbp_add_user_favorite( $u[1], $t );
+
+		$expected = array( $u[0], $u[1] );
+		$favoriters = bbp_get_topic_favoriters( $t );
+
+		$this->assertEqualSets( $expected, $favoriters );
+
+		// Add topic favorites.
+		bbp_add_user_favorite( $u[2], $t );
+
+		$expected = array( $u[0], $u[1], $u[2] );
+		$favoriters = bbp_get_topic_favoriters( $t );
+
+		$this->assertEqualSets( $expected, $favoriters );
+
+		// Remove user favorite.
+		bbp_remove_user_favorite( $u[1], $t );
+
+		$expected = array( $u[0], $u[2] );
+		$favoriters = bbp_get_topic_favoriters( $t );
+
+		$this->assertEqualSets( $expected, $favoriters );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_favorites
+	 */
+	public function test_bbp_get_user_favorites() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic favorites.
+		bbp_add_user_favorite( $u, $t[0] );
+		bbp_add_user_favorite( $u, $t[1] );
+		bbp_add_user_favorite( $u, $t[2] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[1], $t[2] ) ) );
+		$favorites = bbp_get_user_favorites( $u );
+
+		$this->assertEquals( $expected, $favorites );
+
+		// Remove user favorite.
+		bbp_remove_user_favorite( $u, $t[1] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[2] ) ) );
+		$favorites = bbp_get_user_favorites( $u );
+
+		$this->assertEquals( $expected, $favorites );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_favorites_topic_ids
+	 */
+	public function test_bbp_get_user_favorites_topic_ids() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic favorites.
+		bbp_add_user_favorite( $u, $t[0] );
+		bbp_add_user_favorite( $u, $t[1] );
+		bbp_add_user_favorite( $u, $t[2] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[1], $t[2] ), $favorites );
+
+		// Remove user favorite.
+		bbp_remove_user_favorite( $u, $t[1] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[2] ), $favorites );
+	}
+
+	/**
+	 * @covers ::bbp_is_user_favorite
+	 */
+	public function test_bbp_is_user_favorite() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+
+		$favorite = bbp_is_user_favorite( $u, $t );
+
+		$this->assertFalse( $favorite );
+
+		// Add topic favorite.
+		bbp_add_user_favorite( $u, $t );
+
+		$favorite = bbp_is_user_favorite( $u, $t );
+
+		$this->assertTrue( $favorite );
+	}
+
+	/**
+	 * @covers ::bbp_add_user_favorite
+	 */
+	public function test_bbp_add_user_favorite() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic favorites.
+		add_post_meta( $t[0], '_bbp_favorite', $u, false );
+
+		// Add user favorite.
+		bbp_add_user_favorite( $u, $t[1] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[1] ), $favorites );
+
+		// Add user favorite.
+		bbp_add_user_favorite( $u, $t[2] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[1], $t[2] ), $favorites );
+	}
+
+	/**
+	 * @covers ::bbp_remove_user_favorite
+	 */
+	public function test_bbp_remove_user_favorite() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic favorites.
+		foreach ( $t as $topic ) {
+			add_post_meta( $topic, '_bbp_favorite', $u );
+		}
+
+		// Remove user favorite.
+		bbp_remove_user_favorite( $u, $t[2] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[1] ), $favorites );
+
+		// Remove user favorite.
+		bbp_remove_user_favorite( $u, $t[1] );
+
+		$favorites = bbp_get_user_favorites_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0] ), $favorites );
+	}
+
+	/**
+	 * @covers ::bbp_favorites_handler
+	 * @todo   Implement test_bbp_favorites_handler().
+	 */
+	public function test_bbp_favorites_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/functions/functions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/functions/functions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/functions/functions.php	(revision 7162)
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * Tests for the user component functions.
+ *
+ * @group users
+ * @group functions
+ */
+ class BBP_Tests_Users_Functions extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_redirect_login
+	 * @todo   Implement test_bbp_redirect_login().
+	 */
+	public function test_bbp_redirect_login() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_is_anonymous
+	 * @todo   Implement test_bbp_is_anonymous().
+	 */
+	public function test_bbp_is_anonymous() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_anonymous_user_data
+	 * @todo   Implement test_bbp_current_anonymous_user_data().
+	 */
+	public function test_bbp_current_anonymous_user_data() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_current_anonymous_user_data
+	 * @todo   Implement test_bbp_get_current_anonymous_user_data().
+	 */
+	public function test_bbp_get_current_anonymous_user_data() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_set_current_anonymous_user_data
+	 * @todo   Implement test_bbp_set_current_anonymous_user_data().
+	 */
+	public function test_bbp_set_current_anonymous_user_data() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_author_ip
+	 * @todo   Implement test_bbp_current_author_ip().
+	 */
+	public function test_bbp_current_author_ip() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_author_ua
+	 * @todo   Implement test_bbp_current_author_ua().
+	 */
+	public function test_bbp_current_author_ua() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_add_user_to_object
+	 */
+	public function test_bbp_add_user_to_object() {
+		$u = $this->factory->user->create_many( 3 );
+		$t = $this->factory->topic->create();
+
+		// Add object terms.
+		foreach ( $u as $k => $v ) {
+			bbp_add_user_to_object( $t, $v, '_bbp_moderator' );
+		}
+
+		$r = get_metadata( 'post', $t, '_bbp_moderator', false );
+
+		$this->assertCount( 3, $r );
+	}
+
+	/**
+	 * @covers ::bbp_remove_user_from_object
+	 */
+	public function test_bbp_remove_user_from_object() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+
+		// Add object terms.
+		add_metadata( 'post', $t, '_bbp_moderator', $u, false );
+
+		$r = get_metadata( 'post', $t, '_bbp_moderator', false );
+
+		$this->assertCount( 1, $r );
+
+		$r = bbp_remove_user_from_object( $t, $u, '_bbp_moderator' );
+
+		$this->assertTrue( $r );
+
+		$r = get_metadata( 'post', $t, '_bbp_moderator', false );
+
+		$this->assertCount( 0, $r );
+	}
+
+	/**
+	 * @covers ::bbp_is_object_of_user
+	 */
+	public function test_bbp_is_object_of_user() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+
+		$r = bbp_is_object_of_user( $t, $u, '_bbp_moderator' );
+
+		$this->assertFalse( $r );
+
+		// Add user id.
+		add_metadata( 'post', $t, '_bbp_moderator', $u, false );
+
+		$r = bbp_is_object_of_user( $t, $u, '_bbp_moderator' );
+
+		$this->assertTrue( $r );
+	}
+
+ 	/**
+	 * @covers ::bbp_edit_user_handler
+	 * @todo   Implement test_bbp_edit_user_handler().
+	 */
+	public function test_bbp_edit_user_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_email_change_handler
+	 * @todo   Implement test_bbp_user_email_change_handler().
+	 */
+	public function test_bbp_user_email_change_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_user_email_send_notification
+	 * @todo   Implement test_bbp_edit_user_email_send_notification().
+	 */
+	public function test_bbp_edit_user_email_send_notification() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_edit_after
+	 * @todo   Implement test_bbp_user_edit_after().
+	 */
+	public function test_bbp_user_edit_after() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_check_user_edit
+	 * @todo   Implement test_bbp_check_user_edit().
+	 */
+	public function test_bbp_check_user_edit() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_forum_enforce_blocked
+	 * @todo   Implement test_bbp_forum_enforce_blocked().
+	 */
+	public function test_bbp_forum_enforce_blocked() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_sanitize_displayed_user_field
+	 * @todo   Implement test_bbp_sanitize_displayed_user_field().
+	 */
+	public function test_bbp_sanitize_displayed_user_field() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_maybe_convert_pass
+	 * @todo   Implement test_bbp_user_maybe_convert_pass().
+	 */
+	public function test_bbp_user_maybe_convert_pass() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/functions/options.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/functions/options.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/functions/options.php	(revision 7162)
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Tests for the user component count functions.
+ *
+ * @group users
+ * @group functions
+ * @group options
+ */
+class BBP_Tests_Users_Functions_Options extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_default_user_options
+	 * @todo   Implement test_bbp_get_default_user_options().
+	 */
+	public function test_bbp_get_default_user_options() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_add_user_options
+	 * @todo   Implement test_bbp_add_user_options().
+	 */
+	public function test_bbp_add_user_options() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_delete_user_options
+	 * @todo   Implement test_bbp_delete_user_options().
+	 */
+	public function test_bbp_delete_user_options() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_setup_user_option_filters
+	 * @todo   Implement test_bbp_setup_user_option_filters().
+	 */
+	public function test_bbp_setup_user_option_filters() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_filter_get_user_option
+	 * @todo   Implement test_bbp_filter_get_user_option().
+	 */
+	public function test_bbp_filter_get_user_option() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_update_user_last_posted
+	 * @todo   Implement test_bbp_update_user_last_posted().
+	 */
+	public function test_bbp_update_user_last_posted() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_last_posted
+	 * @covers ::bbp_get_user_last_posted
+	 * @todo   Implement test_bbp_get_user_last_posted().
+	 */
+	public function test_bbp_get_user_last_posted() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/functions/subscriptions.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/functions/subscriptions.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/functions/subscriptions.php	(revision 7162)
@@ -0,0 +1,379 @@
+<?php
+
+/**
+ * Tests for the user component subscription functions.
+ *
+ * @group users
+ * @group functions
+ * @group subscriptions
+ */
+class BBP_Tests_Users_Functions_Subscriptions extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_get_forum_subscribers
+	 */
+	public function test_bbp_get_forum_subscribers() {
+		$u = $this->factory->user->create_many( 3 );
+		$f = $this->factory->forum->create_many( 2 );
+
+		// Add forum subscriptions.
+		bbp_add_user_forum_subscription( $u[0], $f[0] );
+		bbp_add_user_forum_subscription( $u[1], $f[0] );
+		bbp_add_user_forum_subscription( $u[2], $f[0] );
+
+		$subscribers = bbp_get_forum_subscribers( $f[0] );
+
+		$this->assertEqualSets( array( $u[0], $u[1], $u[2] ), $subscribers );
+
+		// Add forum subscriptions.
+		bbp_add_user_forum_subscription( $u[0], $f[1] );
+		bbp_add_user_forum_subscription( $u[2], $f[1] );
+
+		$subscribers = bbp_get_forum_subscribers( $f[1] );
+
+		$this->assertEqualSets( array( $u[0], $u[2] ), $subscribers );
+	}
+
+	/**
+	 * @covers ::bbp_get_topic_subscribers
+	 */
+	public function test_bbp_get_topic_subscribers() {
+		$u = $this->factory->user->create_many( 3 );
+		$t = $this->factory->topic->create_many( 2 );
+
+		// Add topic subscriptions.
+		bbp_add_user_topic_subscription( $u[0], $t[0] );
+		bbp_add_user_topic_subscription( $u[1], $t[0] );
+		bbp_add_user_topic_subscription( $u[2], $t[0] );
+
+		$subscribers = bbp_get_topic_subscribers( $t[0] );
+
+		$this->assertEqualSets( array( $u[0], $u[1], $u[2] ), $subscribers );
+
+		// Add topic subscriptions.
+		bbp_add_user_topic_subscription( $u[0], $t[1] );
+		bbp_add_user_topic_subscription( $u[2], $t[1] );
+
+		$subscribers = bbp_get_topic_subscribers( $t[1] );
+
+		$this->assertEqualSets( array( $u[0], $u[2] ), $subscribers );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_subscriptions
+	 * @expectedDeprecated bbp_get_user_subscriptions
+	 */
+	public function test_bbp_get_user_subscriptions() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic subscriptions.
+		bbp_add_user_topic_subscription( $u, $t[0] );
+		bbp_add_user_topic_subscription( $u, $t[1] );
+		bbp_add_user_topic_subscription( $u, $t[2] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[1], $t[2] ) ) );
+		$subscriptions = bbp_get_user_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+
+		// Remove topic subscription.
+		bbp_remove_user_topic_subscription( $u, $t[1] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[2] ) ) );
+		$subscriptions = bbp_get_user_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_topic_subscriptions
+	 */
+	public function test_bbp_get_user_topic_subscriptions() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic subscriptions.
+		bbp_add_user_topic_subscription( $u, $t[0] );
+		bbp_add_user_topic_subscription( $u, $t[1] );
+		bbp_add_user_topic_subscription( $u, $t[2] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[1], $t[2] ) ) );
+		$subscriptions = bbp_get_user_topic_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+
+		// Remove topic subscription.
+		bbp_remove_user_topic_subscription( $u, $t[1] );
+
+		$expected = bbp_has_topics( array( 'post__in' => array( $t[0], $t[2] ) ) );
+		$subscriptions = bbp_get_user_topic_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_forum_subscriptions
+	 */
+	public function test_bbp_get_user_forum_subscriptions() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create_many( 3 );
+
+		// Add forum subscriptions.
+		bbp_add_user_forum_subscription( $u, $f[0] );
+		bbp_add_user_forum_subscription( $u, $f[1] );
+		bbp_add_user_forum_subscription( $u, $f[2] );
+
+		$expected = bbp_has_forums( array( 'post__in' => array( $f[0], $f[1], $f[2] ) ) );
+		$subscriptions = bbp_get_user_forum_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+
+		// Remove forum subscription.
+		bbp_remove_user_forum_subscription( $u, $f[1] );
+
+		$expected = bbp_has_forums( array( 'post__in' => array( $f[0], $f[2] ) ) );
+		$subscriptions = bbp_get_user_forum_subscriptions( $u );
+
+		$this->assertEquals( $expected, $subscriptions );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_subscribed_forum_ids
+	 */
+	public function test_bbp_get_user_subscribed_forum_ids() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create_many( 3 );
+
+		// Add forum subscriptions.
+		bbp_add_user_forum_subscription( $u, $f[0] );
+		bbp_add_user_forum_subscription( $u, $f[1] );
+		bbp_add_user_forum_subscription( $u, $f[2] );
+
+		$subscriptions = bbp_get_user_subscribed_forum_ids( $u );
+
+		$this->assertEqualSets( array( $f[0], $f[1], $f[2] ), $subscriptions );
+
+		// Remove forum subscription.
+		bbp_remove_user_forum_subscription( $u, $f[1] );
+
+		$subscriptions = bbp_get_user_subscribed_forum_ids( $u );
+
+		$this->assertEqualSets( array( $f[0], $f[2] ), $subscriptions );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_subscribed_topic_ids
+	 */
+	public function test_bbp_get_user_subscribed_topic_ids() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 3 );
+
+		// Add topic subscriptions.
+		bbp_add_user_topic_subscription( $u, $t[0] );
+		bbp_add_user_topic_subscription( $u, $t[1] );
+		bbp_add_user_topic_subscription( $u, $t[2] );
+
+		$subscriptions = bbp_get_user_subscribed_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[1], $t[2] ), $subscriptions );
+
+		// Remove topic subscription.
+		bbp_remove_user_topic_subscription( $u, $t[1] );
+
+		$subscriptions = bbp_get_user_subscribed_topic_ids( $u );
+
+		$this->assertEqualSets( array( $t[0], $t[2] ), $subscriptions );
+	}
+
+	/**
+	 * @covers ::bbp_is_user_subscribed
+	 */
+	public function test_bbp_is_user_subscribed() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create_many( 2 );
+		$t = $this->factory->topic->create_many( 2 );
+
+		// Add forum subscription.
+		bbp_add_user_forum_subscription( $u, $f[0] );
+
+		$this->assertTrue( bbp_is_user_subscribed( $u, $f[0] ) );
+		$this->assertFalse( bbp_is_user_subscribed( $u, $f[1] ) );
+
+		// Add topic subscription.
+		bbp_add_user_topic_subscription( $u, $t[0] );
+
+		$this->assertTrue( bbp_is_user_subscribed( $u, $t[0] ) );
+		$this->assertFalse( bbp_is_user_subscribed( $u, $t[1] ) );
+	}
+
+	/**
+	 * @covers ::bbp_is_user_subscribed_to_forum
+	 */
+	public function test_bbp_is_user_subscribed_to_forum() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create_many( 2 );
+
+		// Add forum subscription.
+		bbp_add_user_forum_subscription( $u, $f[0] );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_forum( $u, $f[0] ) );
+		$this->assertFalse( bbp_is_user_subscribed_to_forum( $u, $f[1] ) );
+	}
+
+	/**
+	 * @covers ::bbp_is_user_subscribed_to_topic
+	 */
+	public function test_bbp_is_user_subscribed_to_topic() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create_many( 2 );
+
+		// Add topic subscription.
+		bbp_add_user_topic_subscription( $u, $t[0] );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_topic( $u, $t[0] ) );
+		$this->assertFalse( bbp_is_user_subscribed_to_topic( $u, $t[1] ) );
+	}
+
+	/**
+	 * @covers ::bbp_add_user_subscription
+	 */
+	public function test_bbp_add_user_subscription() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Add forum subscription.
+		bbp_add_user_subscription( $u, $f );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_forum( $u, $f ) );
+
+		// Add topic subscription.
+		bbp_add_user_subscription( $u, $t );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_topic( $u, $t ) );
+	}
+
+	/**
+	 * @covers ::bbp_add_user_forum_subscription
+	 */
+	public function test_bbp_add_user_forum_subscription() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+
+		// Add forum subscription.
+		bbp_add_user_forum_subscription( $u, $f );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_forum( $u, $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_add_user_topic_subscription
+	 */
+	public function test_bbp_add_user_topic_subscription() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+
+		// Add forum subscription.
+		bbp_add_user_topic_subscription( $u, $t );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_topic( $u, $t ) );
+	}
+
+	/**
+	 * @covers ::bbp_remove_user_subscription
+	 */
+	public function test_bbp_remove_user_subscription() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+		$t = $this->factory->topic->create( array(
+			'post_parent' => $f,
+			'topic_meta' => array(
+				'forum_id' => $f,
+			),
+		) );
+
+		// Add forum subscription.
+		bbp_add_user_subscription( $u, $f );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_forum( $u, $f ) );
+
+		// Remove forum subscription.
+		bbp_remove_user_subscription( $u, $f );
+
+		$this->assertFalse( bbp_is_user_subscribed_to_forum( $u, $f ) );
+
+		// Add topic subscription.
+		bbp_add_user_subscription( $u, $t );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_topic( $u, $t ) );
+
+		// Remove topic subscription.
+		bbp_remove_user_subscription( $u, $t );
+
+		$this->assertFalse( bbp_is_user_subscribed_to_topic( $u, $t ) );
+	}
+
+	/**
+	 * @covers ::bbp_remove_user_forum_subscription
+	 */
+	public function test_bbp_remove_user_forum_subscription() {
+		$u = $this->factory->user->create();
+		$f = $this->factory->forum->create();
+
+		// Add forum subscription.
+		bbp_add_user_forum_subscription( $u, $f );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_forum( $u, $f ) );
+
+		// Remove forum subscription.
+		bbp_remove_user_forum_subscription( $u, $f );
+
+		$this->assertFalse( bbp_is_user_subscribed_to_forum( $u, $f ) );
+	}
+
+	/**
+	 * @covers ::bbp_remove_user_topic_subscription
+	 */
+	public function test_bbp_remove_user_topic_subscription() {
+		$u = $this->factory->user->create();
+		$t = $this->factory->topic->create();
+
+		// Add forum subscription.
+		bbp_add_user_topic_subscription( $u, $t );
+
+		$this->assertTrue( bbp_is_user_subscribed_to_topic( $u, $t ) );
+
+		// Remove topic subscription.
+		bbp_remove_user_topic_subscription( $u, $t );
+
+		$this->assertFalse( bbp_is_user_subscribed_to_topic( $u, $t ) );
+	}
+
+	/**
+	 * @covers ::bbp_forum_subscriptions_handler
+	 * @todo   Implement test_bbp_forum_subscriptions_handler().
+	 */
+	public function test_bbp_forum_subscriptions_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_subscriptions_handler
+	 * @todo   Implement test_bbp_subscriptions_handler().
+	 */
+	public function test_bbp_subscriptions_handler() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/template/counts.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/template/counts.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/template/counts.php	(revision 7162)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Tests for the user component count template functions.
+ *
+ * @group users
+ * @group template
+ * @group counts
+ */
+class BBP_Tests_Users_Template_Counts extends BBP_UnitTestCase {
+
+	/**
+	 * @covers ::bbp_user_id
+	 * @covers ::bbp_get_user_id
+	 */
+	public function test_bbp_get_user_id() {
+		$u = $this->factory->user->create();
+
+		$user_id = bbp_get_user_id( $u );
+		$this->assertSame( $u, $user_id );
+	}
+}
Index: /tags/2.6.6/tests/phpunit/testcases/users/template/user.php
===================================================================
--- /tags/2.6.6/tests/phpunit/testcases/users/template/user.php	(revision 7162)
+++ /tags/2.6.6/tests/phpunit/testcases/users/template/user.php	(revision 7162)
@@ -0,0 +1,837 @@
+<?php
+/**
+ * Tests for the users component user template functions.
+ *
+ * @group users
+ * @group template
+ * @group user
+ */
+class BBP_Tests_Users_Template_User extends BBP_UnitTestCase {
+
+	protected $old_current_user = 0;
+
+	public function setUp() {
+		parent::setUp();
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
+		$this->keymaster_id = get_current_user_id();
+		$this->keymaster_userdata = get_userdata( $this->keymaster_id );
+		bbp_set_user_role( $this->keymaster_id, bbp_get_keymaster_role() );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @covers ::bbp_user_id
+	 * @covers ::bbp_get_user_id
+	 */
+	public function test_bbp_get_user_id() {
+		$int_value = $this->keymaster_userdata->ID;
+		$formatted_value = bbp_number_format( $int_value );
+
+		// Integer.
+		$user_id = bbp_get_user_id( $this->keymaster_id );
+		$this->assertSame( $this->keymaster_id, $user_id );
+
+		// Output.
+		$this->expectOutputString( $formatted_value );
+		bbp_user_id( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_current_user_id
+	 * @covers ::bbp_get_current_user_id
+	 */
+	public function test_bbp_get_current_user_id() {
+		$int_value = $this->keymaster_userdata->ID;
+		$formatted_value = bbp_number_format( $int_value );
+
+		// Integer.
+		$user_id = bbp_get_current_user_id();
+		$this->assertSame( $this->keymaster_id, $user_id );
+
+		// Output.
+		$this->expectOutputString( $formatted_value );
+		bbp_current_user_id( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_displayed_user_id
+	 * @covers ::bbp_get_displayed_user_id
+	 * @todo   Implement test_bbp_get_displayed_user_id().
+	 */
+	public function test_bbp_get_displayed_user_id() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_displayed_user_field
+	 * @covers ::bbp_get_displayed_user_field
+	 * @todo   Implement test_bbp_get_displayed_user_field().
+	 */
+	public function test_bbp_get_displayed_user_field() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_name
+	 * @covers ::bbp_get_current_user_name
+	 */
+	public function test_bbp_get_current_user_name() {
+		$current_user = wp_get_current_user();
+
+		// String.
+		$user_id = bbp_get_current_user_name();
+		$this->assertSame( $current_user->display_name, $user_id );
+
+		// Output.
+		$this->expectOutputString( $current_user->display_name );
+		bbp_current_user_name( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_current_user_avatar
+	 * @covers ::bbp_get_current_user_avatar
+	 */
+	public function test_bbp_get_current_user_avatar() {
+		$current_user = get_current_user_id();
+		$size = 40;
+		$wp_avatar = get_avatar( $current_user, $size );
+
+		// String.
+		$bbp_avatar = bbp_get_current_user_avatar( $size );
+		$this->assertSame( $bbp_avatar, $wp_avatar );
+
+		// Output.
+		$this->expectOutputString( $wp_avatar );
+		bbp_current_user_avatar( $size );
+	}
+
+	/**
+	 * @covers ::bbp_user_profile_link
+	 * @covers ::bbp_get_user_profile_link
+	 */
+	public function test_bbp_get_user_profile_link() {
+		$display_name = $this->keymaster_userdata->display_name;
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+
+		$profile_link      = '<a href="http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/">' . $display_name . '</a>';
+		$user_profile_link = bbp_get_user_profile_link( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_link, $user_profile_link );
+
+		// Output.
+		$this->expectOutputString( $profile_link );
+		bbp_user_profile_link( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+
+		$profile_link      = '<a href="http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '">' . $display_name . '</a>';
+		$user_profile_link = bbp_get_user_profile_link( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_link, $user_profile_link );
+
+		// Output.
+		$this->expectOutputString( $profile_link );
+		bbp_user_profile_link( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_user_nicename
+	 * @covers ::bbp_get_user_nicename
+	 */
+	public function test_bbp_get_user_nicename() {
+		$user_nicename = $this->keymaster_userdata->user_nicename;
+
+		// String.
+		$this->assertSame( $user_nicename, bbp_get_user_nicename( $this->keymaster_id ) );
+
+		// Output.
+		$this->expectOutputString( $user_nicename );
+		bbp_user_nicename( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_user_profile_url
+	 * @covers ::bbp_get_user_profile_url
+	 */
+	public function test_bbp_get_user_profile_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$profile_url      = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/';
+		$user_profile_url = bbp_get_user_profile_url( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_url, $user_profile_url );
+
+		// Output.
+		$this->expectOutputString( $profile_url );
+		bbp_user_profile_url( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+
+		$profile_url      = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id;
+		$user_profile_url = bbp_get_user_profile_url( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_url, $user_profile_url );
+
+		// Output.
+		$this->expectOutputString( $profile_url );
+		bbp_user_profile_url( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_user_profile_edit_link
+	 * @covers ::bbp_get_user_profile_edit_link
+	 */
+	public function test_bbp_get_user_profile_edit_link() {
+		$display_name = $this->keymaster_userdata->display_name;
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$profile_edit_link      = '<a href="http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/edit/">' . $display_name . '</a>';
+		$user_profile_edit_link = bbp_get_user_profile_edit_link( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_edit_link, $user_profile_edit_link );
+
+		// Output.
+		$this->expectOutputString( $profile_edit_link );
+		bbp_user_profile_edit_link( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$profile_edit_link      = '<a href="http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&#038;edit=1">' . $display_name . '</a>';
+		$user_profile_edit_link = bbp_get_user_profile_edit_link( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $profile_edit_link, $user_profile_edit_link );
+
+		// Output.
+		$this->expectOutputString( $profile_edit_link );
+		bbp_user_profile_edit_link( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_user_profile_edit_url
+	 */
+	public function test_bbp_user_profile_edit_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$profile_edit_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/edit/';
+
+		// Output.
+		$this->expectOutputString( $profile_edit_url );
+		bbp_user_profile_edit_url( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$profile_edit_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&#038;edit=1';
+
+		// Output.
+		$this->expectOutputString( $profile_edit_url );
+		bbp_user_profile_edit_url( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_profile_edit_url
+	 */
+	public function test_bbp_get_user_profile_edit_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$profile_edit_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/edit/';
+
+		// String.
+		$this->assertSame( $profile_edit_url, bbp_get_user_profile_edit_url( $this->keymaster_id ) );
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$profile_edit_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&edit=1';
+
+		// String.
+		$this->assertSame( $profile_edit_url, bbp_get_user_profile_edit_url( $this->keymaster_id ) );
+	}
+
+	/**
+	 * @covers ::bbp_user_display_role
+	 * @covers ::bbp_get_user_display_role
+	 */
+	public function test_bbp_get_user_display_role() {
+		$display_role = 'Keymaster';
+
+		// String.
+		$this->assertSame( $display_role, bbp_get_user_display_role( $this->keymaster_id ) );
+
+		// Output.
+		$this->expectOutputString( $display_role );
+		bbp_user_display_role( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_admin_link
+	 * @covers ::bbp_get_admin_link
+	 */
+	public function test_bbp_get_admin_link() {
+		$admin_link = '<a href="http://' . WP_TESTS_DOMAIN . '/wp-admin/">Admin</a>';
+
+		$user_admin_link = bbp_get_admin_link( $this->keymaster_id );
+
+		// String.
+		$this->assertSame( $admin_link, $user_admin_link );
+
+		// Output.
+		$this->expectOutputString( $admin_link );
+		bbp_admin_link( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_author_ip
+	 * @covers ::bbp_get_author_ip
+	 */
+	public function test_bbp_get_author_ip() {
+		$t = $this->factory->topic->create();
+
+		$author_ip = '<span class="bbp-author-ip">(127.0.0.1)</span>';
+
+		// String.
+		$this->assertSame( $author_ip, bbp_get_author_ip( $t ) );
+
+		// Output.
+		$this->expectOutputString( $author_ip );
+		bbp_author_ip( $t );
+	}
+
+	/**
+	 * @covers ::bbp_author_display_name
+	 * @covers ::bbp_get_author_display_name
+	 * @todo   Implement test_bbp_get_author_display_name().
+	 */
+	public function test_bbp_get_author_display_name() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_author_email
+	 * @covers ::bbp_get_author_email
+	 * @todo   Implement test_bbp_get_author_email().
+	 */
+	public function test_bbp_get_author_email() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_author_url
+	 * @covers ::bbp_get_author_url
+	 * @todo   Implement test_bbp_get_author_url().
+	 */
+	public function test_bbp_get_author_url() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+ 	/**
+ 	 * @covers ::bbp_favorites_permalink
+	 */
+	public function test_bbp_favorites_permalink() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$favorites_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/favorites/';
+
+		// Output.
+		$this->expectOutputString( $favorites_url );
+		bbp_favorites_permalink( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$favorites_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&#038;bbp_favs=favorites';
+
+		// Output.
+		$this->expectOutputString( $favorites_url );
+		bbp_favorites_permalink( $this->keymaster_id );
+	}
+
+	/**
+ 	 * @covers ::bbp_get_favorites_permalink
+ 	 */
+ 	public function test_bbp_get_favorites_permalink() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$favorites_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/favorites/';
+
+		// String.
+		$this->assertSame( $favorites_url, bbp_get_favorites_permalink( $this->keymaster_id ) );
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$favorites_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&bbp_favs=favorites';
+
+		// String.
+		$this->assertSame( $favorites_url, bbp_get_favorites_permalink( $this->keymaster_id ) );
+ 	}
+
+	/**
+	 * @covers ::bbp_user_favorites_link
+	 * @covers ::bbp_get_user_favorites_link
+	 * @todo   Implement test_bbp_get_user_favorites_link().
+	 */
+	public function test_bbp_get_user_favorites_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+ 	/**
+ 	 * @covers ::bbp_subscriptions_permalink
+	 */
+	public function test_bbp_subscriptions_permalink() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$subscriptions_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/subscriptions/';
+
+		// Output.
+		$this->expectOutputString( $subscriptions_url );
+		bbp_subscriptions_permalink( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$subscriptions_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&#038;bbp_subs=subscriptions';
+
+		// Output.
+		$this->expectOutputString( $subscriptions_url );
+		bbp_subscriptions_permalink( $this->keymaster_id );
+	}
+
+	/**
+ 	 * @covers ::bbp_get_subscriptions_permalink
+ 	 */
+ 	public function test_bbp_get_subscriptions_permalink() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$subscriptions_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/subscriptions/';
+
+		// String.
+		$this->assertSame( $subscriptions_url, bbp_get_subscriptions_permalink( $this->keymaster_id ) );
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$subscriptions_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&bbp_subs=subscriptions';
+
+		// String.
+		$this->assertSame( $subscriptions_url, bbp_get_subscriptions_permalink( $this->keymaster_id ) );
+ 	}
+
+	/**
+	 * @covers ::bbp_user_subscribe_link
+	 * @covers ::bbp_get_user_subscribe_link
+	 * @todo   Implement test_bbp_get_user_subscribe_link().
+	 */
+	public function test_bbp_get_user_subscribe_link() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_notice_edit_user_success
+	 * @todo   Implement test_bbp_notice_edit_user_success().
+	 */
+	public function test_bbp_notice_edit_user_success() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_notice_edit_user_pending_email
+	 * @todo   Implement test_bbp_notice_edit_user_pending_email().
+	 */
+	public function test_bbp_notice_edit_user_pending_email() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_notice_edit_user_is_super_admin
+	 * @todo   Implement test_bbp_notice_edit_user_is_super_admin().
+	 */
+	public function test_bbp_notice_edit_user_is_super_admin() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_user_display_name
+	 * @todo   Implement test_bbp_edit_user_display_name().
+	 */
+	public function test_bbp_edit_user_display_name() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_user_blog_role
+	 * @todo   Implement test_bbp_edit_user_blog_role().
+	 */
+	public function test_bbp_edit_user_blog_role() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_user_forums_role
+	 * @todo   Implement test_bbp_edit_user_forums_role().
+	 */
+	public function test_bbp_edit_user_forums_role() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_edit_user_contact_methods
+	 * @todo   Implement test_bbp_edit_user_contact_methods().
+	 */
+	public function test_bbp_edit_user_contact_methods() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_topics_created_url
+	 */
+	public function test_bbp_user_topics_created_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$topics_created_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/topics/';
+
+		// Output.
+		$this->expectOutputString( $topics_created_url );
+		bbp_user_topics_created_url( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$topics_created_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&#038;bbp_tops=1';
+
+		// Output.
+		$this->expectOutputString( $topics_created_url );
+		bbp_user_topics_created_url( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_topics_created_url
+	 */
+	public function test_bbp_get_user_topics_created_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$topics_created_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/topics/';
+
+		// String.
+		$this->assertSame( $topics_created_url, bbp_get_user_topics_created_url( $this->keymaster_id ) );
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$topics_created_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '&bbp_tops=1';
+
+		// String.
+		$this->assertSame( $topics_created_url, bbp_get_user_topics_created_url( $this->keymaster_id ) );
+	}
+
+	/**
+	 * @covers ::bbp_user_replies_created_url
+	 */
+	public function test_bbp_user_replies_created_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$replies_created_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/replies/';
+
+		// Output.
+		$this->expectOutputString( $replies_created_url );
+		bbp_user_replies_created_url( $this->keymaster_id );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$replies_created_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user='. $this->keymaster_id . '&#038;bbp_reps=1';
+
+		// Output.
+		$this->expectOutputString( $replies_created_url );
+		bbp_user_replies_created_url( $this->keymaster_id );
+	}
+
+	/**
+	 * @covers ::bbp_get_user_replies_created_url
+	 */
+	public function test_bbp_get_user_replies_created_url() {
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$replies_created_url = 'http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/replies/';
+
+		// String.
+		$this->assertSame( $replies_created_url, bbp_get_user_replies_created_url( $this->keymaster_id ) );
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$replies_created_url = 'http://' . WP_TESTS_DOMAIN . '/?bbp_user='. $this->keymaster_id . '&bbp_reps=1';
+
+		// String.
+		$this->assertSame( $replies_created_url, bbp_get_user_replies_created_url( $this->keymaster_id ) );
+	}
+
+	/**
+	 * @covers ::bbp_login_notices
+	 * @todo   Implement test_bbp_login_notices().
+	 */
+	public function test_bbp_login_notices() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_logged_in_redirect
+	 * @todo   Implement test_bbp_logged_in_redirect().
+	 */
+	public function test_bbp_logged_in_redirect() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_login_fields
+	 * @todo   Implement test_bbp_user_login_fields().
+	 */
+	public function test_bbp_user_login_fields() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_register_fields
+	 * @todo   Implement test_bbp_user_register_fields().
+	 */
+	public function test_bbp_user_register_fields() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_user_lost_pass_fields
+	 * @todo   Implement test_bbp_user_lost_pass_fields().
+	 */
+	public function test_bbp_user_lost_pass_fields() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_author_link
+	 * @covers ::bbp_get_author_link
+	 */
+	public function test_bbp_get_author_link() {
+		$t = $this->factory->topic->create();
+
+		$display_name = $this->keymaster_userdata->display_name;
+		$current_user = get_current_user_id();
+		$size = 80;
+		$wp_avatar = get_avatar( $current_user, $size );
+
+		// Pretty permalinks
+		$this->set_permalink_structure( '/%postname%/' );
+		$author_link = '<a href="http://' . WP_TESTS_DOMAIN . '/forums/users/' . $this->keymaster_userdata->user_nicename . '/" title="View ' . $display_name . '&#039;s profile" class="bbp-author-link"><span class="bbp-author-avatar">' . $wp_avatar .
+			'</span>&nbsp;<span class="bbp-author-name">' . $display_name . '</span></a>';
+
+		// String.
+		$this->assertSame( $author_link, bbp_get_author_link( $t ) );
+
+		// Output.
+		$this->expectOutputString( $author_link );
+		bbp_author_link( $t );
+
+		ob_clean();
+
+		// Ugly permalinks
+		$this->set_permalink_structure();
+		$author_link = '<a href="http://' . WP_TESTS_DOMAIN . '/?bbp_user=' . $this->keymaster_id . '" title="View ' . $display_name .
+			'&#039;s profile" class="bbp-author-link"><span class="bbp-author-avatar">' . $wp_avatar .
+			'</span>&nbsp;<span class="bbp-author-name">' . $display_name . '</span></a>';
+
+		// String.
+		$this->assertSame( $author_link, bbp_get_author_link( $t ) );
+
+		// Output.
+		$this->expectOutputString( $author_link );
+		bbp_author_link( $t );
+	}
+
+	/**
+	 * @covers ::bbp_user_can_view_forum
+	 * @todo   Implement test_bbp_user_can_view_forum().
+	 */
+	public function test_bbp_user_can_view_forum() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_publish_forums
+	 * @todo   Implement test_bbp_current_user_can_publish_forums().
+	 */
+	public function test_bbp_current_user_can_publish_forums() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_publish_topics
+	 * @todo   Implement test_bbp_current_user_can_publish_topics().
+	 */
+	public function test_bbp_current_user_can_publish_topics() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_publish_replies
+	 * @todo   Implement test_bbp_current_user_can_publish_replies().
+	 */
+	public function test_bbp_current_user_can_publish_replies() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_get_forums_for_current_user
+	 * @todo   Implement test_bbp_get_forums_for_current_user().
+	 */
+	public function test_bbp_get_forums_for_current_user() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_access_create_forum_form
+	 * @todo   Implement test_bbp_current_user_can_access_create_forum_form().
+	 */
+	public function test_bbp_current_user_can_access_create_forum_form() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_access_create_topic_form
+	 * @todo   Implement test_bbp_current_user_can_access_create_topic_form().
+	 */
+	public function test_bbp_current_user_can_access_create_topic_form() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_access_create_reply_form
+	 * @todo   Implement test_bbp_current_user_can_access_create_reply_form().
+	 */
+	public function test_bbp_current_user_can_access_create_reply_form() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+
+	/**
+	 * @covers ::bbp_current_user_can_access_anonymous_user_form
+	 * @todo   Implement test_bbp_current_user_can_access_anonymous_user_form().
+	 */
+	public function test_bbp_current_user_can_access_anonymous_user_form() {
+		// Remove the following lines when you implement this test.
+		$this->markTestIncomplete(
+			'This test has not been implemented yet.'
+		);
+	}
+}
